From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001
From: Daniel Baumann
Date: Wed, 15 May 2024 05:35:49 +0200
Subject: Merging upstream version 126.0.
Signed-off-by: Daniel Baumann
---
mobile/android/focus-android/.buildconfig.yml | 165 +
mobile/android/focus-android/.editorconfig | 5 +
mobile/android/focus-android/CODEOWNERS | 5 +
mobile/android/focus-android/CONTRIBUTING.md | 4 +
mobile/android/focus-android/README.md | 119 +
mobile/android/focus-android/Screengrabfile | 22 +
.../android/focus-android/app/.experimenter.yaml | 23 +
mobile/android/focus-android/app/.gitignore | 3 +
mobile/android/focus-android/app/build.gradle | 788 +++
mobile/android/focus-android/app/lint-baseline.xml | 7196 ++++++++++++++++++++
mobile/android/focus-android/app/lint.xml | 27 +
mobile/android/focus-android/app/metrics.yaml | 2458 +++++++
mobile/android/focus-android/app/nimbus.fml.yaml | 48 +
mobile/android/focus-android/app/pings.yaml | 31 +
.../android/focus-android/app/proguard-rules.pro | 154 +
.../app/src/androidTest/assets/audioPage.html | 37 +
.../src/androidTest/assets/cross-site-cookies.html | 12 +
.../app/src/androidTest/assets/download.jpg | Bin 0 -> 9375 bytes
.../androidTest/assets/etpPages/adsTrackers.html | 21 +
.../assets/etpPages/analyticsTrackers.html | 21 +
.../androidTest/assets/etpPages/otherTrackers.html | 22 +
.../assets/etpPages/socialTrackers.html | 21 +
.../app/src/androidTest/assets/genericPage.html | 15 +
.../androidTest/assets/global_privacy_control.html | 15 +
.../app/src/androidTest/assets/htmlControls.html | 66 +
.../app/src/androidTest/assets/image_test.html | 20 +
.../app/src/androidTest/assets/mutedVideoPage.html | 53 +
.../app/src/androidTest/assets/rabbit.jpg | Bin 0 -> 5038 bytes
.../androidTest/assets/resources/audioSample.mp3 | Bin 0 -> 5517 bytes
.../app/src/androidTest/assets/resources/clip.mp4 | Bin 0 -> 39160 bytes
.../src/androidTest/assets/same-site-cookies.html | 125 +
.../app/src/androidTest/assets/service-worker.js | 2 +
.../app/src/androidTest/assets/storage_check.html | 23 +
.../app/src/androidTest/assets/storage_start.html | 28 +
.../app/src/androidTest/assets/tab1.html | 29 +
.../app/src/androidTest/assets/tab2.html | 16 +
.../app/src/androidTest/assets/tab3.html | 20 +
.../app/src/androidTest/assets/test.html | 38 +
.../app/src/androidTest/assets/videoPage.html | 53 +
.../mozilla/focus/activity/AddToHomescreenTest.kt | 89 +
.../org/mozilla/focus/activity/ContextMenusTest.kt | 187 +
.../org/mozilla/focus/activity/CustomTabTest.kt | 133 +
.../org/mozilla/focus/activity/DownloadFileTest.kt | 200 +
.../EnhancedTrackingProtectionSettingsTest.kt | 351 +
.../focus/activity/EraseBrowsingDataTest.kt | 160 +
.../org/mozilla/focus/activity/ErrorPagesTest.kt | 60 +
.../org/mozilla/focus/activity/FirstRunTest.kt | 57 +
.../mozilla/focus/activity/MediaPlaybackTest.kt | 101 +
.../focus/activity/MozillaSupportPagesTest.kt | 128 +
.../org/mozilla/focus/activity/MultitaskingTest.kt | 147 +
.../org/mozilla/focus/activity/OldFirstRunTest.kt | 67 +
.../activity/OpenInExternalBrowserDialogueTest.kt | 61 +
.../org/mozilla/focus/activity/SafeBrowsingTest.kt | 139 +
.../java/org/mozilla/focus/activity/SearchTest.kt | 184 +
.../mozilla/focus/activity/SettingsAdvancedTest.kt | 82 +
.../mozilla/focus/activity/SettingsGeneralTest.kt | 178 +
.../mozilla/focus/activity/SettingsPrivacyTest.kt | 135 +
.../org/mozilla/focus/activity/SettingsTest.kt | 85 +
.../org/mozilla/focus/activity/ShortcutsTest.kt | 124 +
.../mozilla/focus/activity/SitePermissionsTest.kt | 272 +
.../mozilla/focus/activity/SwitchContextTest.kt | 132 +
.../mozilla/focus/activity/ThreeDotMainMenuTest.kt | 137 +
.../mozilla/focus/activity/URLAutocompleteTest.kt | 147 +
.../org/mozilla/focus/activity/WebControlsTest.kt | 174 +
.../focus/activity/robots/AddToHomeScreenRobot.kt | 71 +
.../mozilla/focus/activity/robots/BrowserRobot.kt | 694 ++
.../focus/activity/robots/CustomTabRobot.kt | 132 +
.../mozilla/focus/activity/robots/DownloadRobot.kt | 96 +
.../focus/activity/robots/HomeScreenRobot.kt | 235 +
.../focus/activity/robots/NotificationRobot.kt | 159 +
.../mozilla/focus/activity/robots/SearchRobot.kt | 166 +
.../activity/robots/SettingsAdvancedMenuRobot.kt | 109 +
.../activity/robots/SettingsGeneralMenuRobot.kt | 197 +
.../activity/robots/SettingsMozillaMenuRobot.kt | 187 +
.../activity/robots/SettingsPrivacyMenuRobot.kt | 741 ++
.../mozilla/focus/activity/robots/SettingsRobot.kt | 114 +
.../activity/robots/SettingsSearchMenuRobot.kt | 169 +
.../robots/SettingsSitePermissionsRobot.kt | 166 +
.../activity/robots/SiteSecurityInfoSheetRobot.kt | 75 +
.../mozilla/focus/activity/robots/TabsTrayRobot.kt | 69 +
.../focus/activity/robots/ThreeDotMainMenuRobot.kt | 238 +
.../java/org/mozilla/focus/helpers/Constants.kt | 9 +
.../org/mozilla/focus/helpers/DeleteFilesHelper.kt | 52 +
.../org/mozilla/focus/helpers/EspressoHelper.kt | 84 +
.../mozilla/focus/helpers/FeatureSettingsHelper.kt | 51 +
.../helpers/HostScreencapScreenshotStrategy.java | 116 +
.../mozilla/focus/helpers/MainActivityTestRule.kt | 151 +
.../focus/helpers/MockLocationUpdatesRule.kt | 113 +
.../mozilla/focus/helpers/MockWebServerHelper.kt | 76 +
.../org/mozilla/focus/helpers/RetryTestRule.kt | 44 +
.../org/mozilla/focus/helpers/StringsHelper.kt | 27 +
.../org/mozilla/focus/helpers/TestAssetHelper.kt | 71 +
.../java/org/mozilla/focus/helpers/TestHelper.kt | 379 ++
.../org/mozilla/focus/helpers/ext/WaitNotNull.kt | 20 +
.../idlingResources/RecyclerViewIdlingResource.kt | 32 +
.../idlingResources/SessionLoadedIdlingResource.kt | 48 +
.../focus/privacy/GlobalPrivacyControlTest.kt | 60 +
.../focus/privacy/LocalSessionStorageTest.kt | 96 +
.../focus/screenshots/AllowListScreenshots.java | 123 +
.../screenshots/BrowserScreenScreenshots.java | 301 +
.../focus/screenshots/ErrorPagesScreenshots.java | 96 +
.../focus/screenshots/FirstRunScreenshots.kt | 55 +
.../focus/screenshots/HomeScreenScreenshots.kt | 129 +
.../focus/screenshots/NotificationScreenshots.java | 104 +
.../mozilla/focus/screenshots/ScreenshotTest.java | 94 +
.../focus/screenshots/SettingsScreenshots.kt | 71 +
.../org/mozilla/focus/testAnnotations/SmokeTest.kt | 13 +
.../src/beta/res/drawable-v24/ic_splash_screen.xml | 263 +
.../app/src/beta/res/drawable/ic_splash_screen.png | Bin 0 -> 6437 bytes
.../app/src/beta/res/values-night/colors.xml | 7 +
.../app/src/debug/AndroidManifest.xml | 23 +
.../org/mozilla/focus/DebugFocusApplication.kt | 40 +
.../java/org/mozilla/focus/utils/AdjustHelper.java | 14 +
.../src/debug/java/org/mozilla/focus/web/Config.kt | 10 +
.../debug/res/mipmap-anydpi-v26/ic_launcher.xml | 9 +
.../app/src/debug/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 14925 bytes
.../res/mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 14454 bytes
.../app/src/debug/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 16819 bytes
.../res/mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 18016 bytes
.../src/debug/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 21584 bytes
.../res/mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 25726 bytes
.../src/debug/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 24098 bytes
.../res/mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 29236 bytes
.../app/src/focusBeta/ic_launcher-playstore.png | Bin 0 -> 115595 bytes
.../java/org/mozilla/focus/utils/AdjustHelper.java | 14 +
.../res/drawable-land/dark_background.xml | 29 +
.../res/drawable-v24/ic_launcher_foreground.xml | 262 +
.../focusBeta/res/drawable-v24/icon_foreground.xml | 253 +
.../src/focusBeta/res/drawable/dark_background.xml | 29 +
.../res/drawable/ic_launcher_background.xml | 78 +
.../src/focusBeta/res/drawable/onboarding_logo.xml | 263 +
.../app/src/focusBeta/res/drawable/wordmark2.xml | 272 +
.../res/mipmap-anydpi-v26/ic_launcher.xml | 9 +
.../res/mipmap-anydpi-v26/ic_launcher_round.xml | 9 +
.../src/focusBeta/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 5962 bytes
.../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 9028 bytes
.../src/focusBeta/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3309 bytes
.../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 4965 bytes
.../src/focusBeta/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 8920 bytes
.../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 13502 bytes
.../focusBeta/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 15094 bytes
.../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 22454 bytes
.../focusBeta/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 21909 bytes
.../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 33006 bytes
.../app/src/focusBeta/res/values/app.xml | 7 +
.../app/src/focusBeta/res/xml-v25/shortcuts.xml | 31 +
.../app/src/focusDebug/res/xml-v25/shortcuts.xml | 31 +
.../app/src/focusNightly/res/values/app.xml | 8 +
.../app/src/focusNightly/res/xml-v25/shortcuts.xml | 31 +
.../app/src/focusRelease/AndroidManifest.xml | 24 +
.../java/org/mozilla/focus/utils/AdjustHelper.java | 72 +
.../java/org/mozilla/focus/web/Config.kt | 10 +
.../app/src/focusRelease/res/xml-v25/shortcuts.xml | 31 +
.../klar/res/drawable/background_gradient_dark.xml | 11 +
.../app/src/klar/res/drawable/wordmark2.xml | 357 +
.../focus-android/app/src/klar/res/values/app.xml | 11 +
.../java/org/mozilla/focus/utils/AdjustHelper.java | 14 +
.../src/klarBeta/res/drawable/onboarding_logo.xml | 263 +
.../app/src/klarBeta/res/xml-v25/shortcuts.xml | 31 +
.../app/src/klarDebug/res/xml-v25/shortcuts.xml | 31 +
.../klarNightly/drawable-v24/icon_foreground.xml | 253 +
.../drawable/background_gradient_dark.xml | 11 +
.../src/klarNightly/drawable/icon_background.xml | 18 +
.../drawable/toolbar_url_background.xml | 8 +
.../klarNightly/mipmap-anydpi-v26/ic_launcher.xml | 8 +
.../mipmap-anydpi-v26/ic_launcher_round.xml | 8 +
.../src/klarNightly/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4230 bytes
.../klarNightly/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6819 bytes
.../src/klarNightly/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2521 bytes
.../klarNightly/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3997 bytes
.../src/klarNightly/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6134 bytes
.../klarNightly/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 10083 bytes
.../src/klarNightly/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 10218 bytes
.../mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 16745 bytes
.../src/klarNightly/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 15031 bytes
.../mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 24870 bytes
.../app/src/klarNightly/res/xml-v25/shortcuts.xml | 31 +
.../java/org/mozilla/focus/utils/AdjustHelper.java | 14 +
.../java/org/mozilla/focus/web/Config.kt | 10 +
.../app/src/klarRelease/res/xml-v25/shortcuts.xml | 31 +
.../focus-android/app/src/main/AndroidManifest.xml | 229 +
.../app/src/main/assets/error_style.css | 172 +
.../focus-android/app/src/main/assets/style.css | 43 +
.../app/src/main/ic_launcher-playstore.png | Bin 0 -> 99479 bytes
.../src/main/java/org/mozilla/focus/Components.kt | 321 +
.../java/org/mozilla/focus/FocusApplication.kt | 238 +
.../mozilla/focus/activity/CrashListActivity.kt | 24 +
.../mozilla/focus/activity/CustomTabActivity.kt | 95 +
.../focus/activity/EraseAndOpenShortcutActivity.kt | 31 +
.../focus/activity/EraseShortcutActivity.kt | 23 +
.../focus/activity/InstallFirefoxActivity.kt | 93 +
.../focus/activity/IntentReceiverActivity.kt | 72 +
.../org/mozilla/focus/activity/MainActivity.kt | 473 ++
.../mozilla/focus/activity/TextActionActivity.kt | 48 +
.../focus/animation/TransitionDrawableGroup.kt | 26 +
.../org/mozilla/focus/appreview/AppReviewStep.kt | 11 +
.../org/mozilla/focus/appreview/AppReviewUtils.kt | 151 +
.../focus/autocomplete/AutocompleteAddFragment.kt | 120 +
.../AutocompleteCustomDomainsPreference.kt | 23 +
.../AutocompleteDefaultDomainsPreference.kt | 23 +
.../autocomplete/AutocompleteDomainFormatter.kt | 18 +
.../focus/autocomplete/AutocompleteListFragment.kt | 349 +
.../autocomplete/AutocompleteRemoveFragment.kt | 74 +
.../autocomplete/AutocompleteSettingsFragment.kt | 84 +
.../biometrics/BiometricAuthenticationFragment.kt | 131 +
.../BiometricAuthenticationFragmentCompose.kt | 114 +
.../org/mozilla/focus/biometrics/LockObserver.kt | 59 +
.../focus/browser/BlockedTrackersMiddleware.kt | 55 +
.../org/mozilla/focus/browser/LocalizedContent.kt | 106 +
.../browser/integration/BrowserMenuController.kt | 190 +
.../integration/BrowserToolbarIntegration.kt | 501 ++
.../browser/integration/FindInPageIntegration.kt | 53 +
.../browser/integration/FullScreenIntegration.kt | 133 +
.../integration/NavigationButtonsIntegration.kt | 123 +
.../java/org/mozilla/focus/cfr/CfrMiddleware.kt | 123 +
.../org/mozilla/focus/components/EngineProvider.kt | 59 +
.../focus/contextmenu/ContextMenuCandidates.kt | 87 +
.../focus/cookiebanner/CookieBannerFragment.kt | 66 +
.../focus/cookiebanner/CookieBannerOption.kt | 28 +
.../CookieBannerRejectAllPreference.kt | 20 +
.../CookieBannerExceptionDetailsSwitch.kt | 32 +
.../CookieBannerReducerDetailsPanel.kt | 181 +
.../cookiebannerreducer/CookieBannerReducerItem.kt | 158 +
.../CookieBannerReducerMiddleware.kt | 230 +
.../CookieBannerReducerStatus.kt | 32 +
.../CookieBannerReducerStore.kt | 113 +
.../DefaultCookieBannerReducerInteractor.kt | 35 +
.../mozilla/focus/customtabs/CustomTabsService.kt | 15 +
.../org/mozilla/focus/downloads/DownloadService.kt | 17 +
.../mozilla/focus/engine/AppContentInterceptor.kt | 111 +
.../java/org/mozilla/focus/engine/ClientWrapper.kt | 29 +
.../engine/EngineSharedPreferencesListener.kt | 95 +
.../mozilla/focus/engine/SanityCheckMiddleware.kt | 36 +
.../focus/exceptions/ExceptionsListFragment.kt | 323 +
.../focus/exceptions/ExceptionsRemoveFragment.kt | 61 +
.../org/mozilla/focus/experiments/NimbusSetup.kt | 116 +
.../main/java/org/mozilla/focus/ext/Activity.kt | 41 +
.../java/org/mozilla/focus/ext/AndroidViewModel.kt | 12 +
.../java/org/mozilla/focus/ext/BrowserStore.kt | 21 +
.../java/org/mozilla/focus/ext/BrowserToolbar.kt | 86 +
.../java/org/mozilla/focus/ext/ContentState.kt | 16 +
.../src/main/java/org/mozilla/focus/ext/Context.kt | 60 +
.../main/java/org/mozilla/focus/ext/Fragment.kt | 47 +
.../mozilla/focus/ext/PreferenceFragmentCompat.kt | 16 +
.../java/org/mozilla/focus/ext/SessionState.kt | 25 +
.../src/main/java/org/mozilla/focus/ext/String.kt | 75 +
.../app/src/main/java/org/mozilla/focus/ext/Uri.kt | 69 +
.../org/mozilla/focus/firstrun/FirstrunCardView.kt | 38 +
.../mozilla/focus/firstrun/FirstrunPagerAdapter.kt | 103 +
.../fragment/AddToHomescreenDialogFragment.kt | 145 +
.../org/mozilla/focus/fragment/BaseFragment.kt | 57 +
.../org/mozilla/focus/fragment/BrowserFragment.kt | 1072 +++
.../focus/fragment/CrashReporterFragment.kt | 40 +
.../org/mozilla/focus/fragment/FirstrunFragment.kt | 150 +
.../org/mozilla/focus/fragment/UrlInputFragment.kt | 632 ++
.../mozilla/focus/fragment/about/AboutFragment.kt | 209 +
.../focus/fragment/about/SecretSettingsUnlocker.kt | 59 +
.../fragment/onboarding/OnboardingController.kt | 100 +
.../fragment/onboarding/OnboardingFirstFragment.kt | 62 +
.../onboarding/OnboardingFirstScreenCompose.kt | 165 +
.../fragment/onboarding/OnboardingInteractor.kt | 34 +
.../onboarding/OnboardingSecondFragment.kt | 88 +
.../onboarding/OnboardingSecondScreenCompose.kt | 194 +
.../focus/fragment/onboarding/OnboardingStep.kt | 12 +
.../focus/fragment/onboarding/OnboardingStorage.kt | 49 +
.../mozilla/focus/input/InputToolbarIntegration.kt | 186 +
.../java/org/mozilla/focus/locale/LocaleManager.kt | 80 +
.../main/java/org/mozilla/focus/locale/Locales.kt | 107 +
.../screen/DefaultLanguageScreenInteractor.kt | 17 +
.../org/mozilla/focus/locale/screen/Language.kt | 15 +
.../focus/locale/screen/LanguageFragment.kt | 115 +
.../focus/locale/screen/LanguageListItem.kt | 16 +
.../focus/locale/screen/LanguageMiddleware.kt | 69 +
.../focus/locale/screen/LanguageScreenStore.kt | 70 +
.../mozilla/focus/locale/screen/LanguageStorage.kt | 90 +
.../focus/locale/screen/LocaleDescriptor.kt | 125 +
.../focus/locale/screen/LocaleFragmentCompose.kt | 175 +
.../org/mozilla/focus/media/MediaSessionService.kt | 20 +
.../java/org/mozilla/focus/menu/ToolbarMenu.kt | 40 +
.../mozilla/focus/menu/browser/CustomTabMenu.kt | 160 +
.../focus/menu/browser/DefaultBrowserMenu.kt | 193 +
.../java/org/mozilla/focus/menu/home/HomeMenu.kt | 35 +
.../org/mozilla/focus/menu/home/HomeMenuItem.kt | 10 +
.../focus/navigation/MainActivityNavigation.kt | 315 +
.../java/org/mozilla/focus/navigation/Navigator.kt | 56 +
.../java/org/mozilla/focus/navigation/StoreLink.kt | 50 +
.../main/java/org/mozilla/focus/open/AppAdapter.kt | 114 +
.../java/org/mozilla/focus/open/AppViewHolder.kt | 45 +
.../mozilla/focus/open/InstallBannerViewHolder.kt | 39 +
.../org/mozilla/focus/open/OpenWithFragment.kt | 145 +
.../java/org/mozilla/focus/perf/Performance.kt | 52 +
.../search/ManualAddSearchEnginePreference.kt | 126 +
.../MultiselectSearchEngineListPreference.kt | 66 +
.../search/RadioSearchEngineListPreference.kt | 64 +
.../focus/search/SearchEngineListPreference.kt | 114 +
.../mozilla/focus/search/SearchEnginePreference.kt | 48 +
.../mozilla/focus/search/SearchFilterMiddleware.kt | 37 +
.../org/mozilla/focus/search/SearchMigration.kt | 71 +
.../SearchSuggestionsPreferences.kt | 44 +
.../SearchSuggestionsViewModel.kt | 116 +
.../focus/searchsuggestions/ui/SearchOverlay.kt | 121 +
.../ui/SearchSuggestionsFragment.kt | 127 +
.../ui/SearchSuggestionsPreference.kt | 31 +
.../focus/searchwidget/ExternalIntentNavigation.kt | 125 +
.../PromoteSearchWidgetDialogCompose.kt | 201 +
.../focus/searchwidget/SearchWidgetProvider.kt | 73 +
.../focus/searchwidget/SearchWidgetUtils.kt | 73 +
.../focus/searchwidget/VoiceSearchActivity.kt | 32 +
.../org/mozilla/focus/session/IntentProcessor.kt | 203 +
.../focus/session/PrivateNotificationFeature.kt | 49 +
.../focus/session/SessionNotificationService.kt | 240 +
.../focus/session/VisibilityLifeCycleCallback.kt | 81 +
.../org/mozilla/focus/session/ui/TabViewHolder.kt | 51 +
.../org/mozilla/focus/session/ui/TabsAdapter.kt | 40 +
.../java/org/mozilla/focus/session/ui/TabsPopup.kt | 78 +
.../focus/settings/AboutLibrariesFragment.kt | 27 +
.../mozilla/focus/settings/BaseComposeFragment.kt | 130 +
.../mozilla/focus/settings/BaseSettingsFragment.kt | 35 +
.../focus/settings/BaseSettingsLikeFragment.kt | 40 +
.../focus/settings/GeneralSettingsFragment.kt | 141 +
.../focus/settings/HttpsOnlyModePreference.kt | 35 +
.../InstalledSearchEnginesSettingsFragment.kt | 138 +
.../focus/settings/LearnMoreSwitchPreference.kt | 58 +
.../ManualAddSearchEngineSettingsFragment.kt | 262 +
.../focus/settings/MozillaSettingsFragment.kt | 97 +
.../focus/settings/RadioButtonPreference.kt | 191 +
.../RemoveSearchEnginesSettingsFragment.kt | 77 +
.../focus/settings/SafeBrowsingSwitchPreference.kt | 19 +
.../focus/settings/SearchSettingsFragment.kt | 70 +
.../org/mozilla/focus/settings/SettingsFragment.kt | 50 +
.../org/mozilla/focus/settings/StatePreference.kt | 47 +
.../settings/advanced/AdvancedSettingsFragment.kt | 80 +
.../settings/advanced/SecretSettingsFragment.kt | 53 +
.../settings/advanced/SharedPreferenceUpdater.kt | 24 +
.../settings/permissions/SitePermissionOption.kt | 44 +
.../permissions/SitePermissionsFragment.kt | 63 +
...DefaultSitePermissionOptionsScreenInteractor.kt | 22 +
.../HardwarePermissionCheckFeature.kt | 20 +
.../permissionoptions/SitePermission.kt | 20 +
.../SitePermissionOptionListItem.kt | 11 +
.../SitePermissionOptionsFragment.kt | 141 +
.../SitePermissionOptionsFragmentCompose.kt | 280 +
.../SitePermissionOptionsScreenStore.kt | 70 +
.../SitePermissionOptionsStorage.kt | 215 +
.../SitePermissionOptionsStorageMiddleware.kt | 43 +
.../settings/privacy/ConnectionDetailsPanel.kt | 82 +
.../focus/settings/privacy/PreferenceSwitch.kt | 68 +
.../settings/privacy/PreferenceToolTipCompose.kt | 137 +
.../privacy/PrivacySecuritySettingsFragment.kt | 254 +
.../settings/privacy/TrackingProtectionPanel.kt | 215 +
.../settings/privacy/studies/StudiesAdapter.kt | 67 +
.../settings/privacy/studies/StudiesFragment.kt | 158 +
.../settings/privacy/studies/StudiesListItem.kt | 16 +
.../privacy/studies/StudiesRecyclerView.kt | 21 +
.../settings/privacy/studies/StudiesViewHolder.kt | 74 +
.../settings/privacy/studies/StudiesViewModel.kt | 65 +
.../java/org/mozilla/focus/shortcut/HomeScreen.kt | 122 +
.../org/mozilla/focus/shortcut/IconGenerator.kt | 135 +
.../main/java/org/mozilla/focus/state/AppAction.kt | 124 +
.../java/org/mozilla/focus/state/AppReducer.kt | 311 +
.../main/java/org/mozilla/focus/state/AppState.kt | 124 +
.../main/java/org/mozilla/focus/state/AppStore.kt | 14 +
.../org/mozilla/focus/telemetry/ActivationPing.kt | 84 +
.../org/mozilla/focus/telemetry/BrowsersCache.kt | 41 +
.../org/mozilla/focus/telemetry/FactsProcessor.kt | 82 +
.../focus/telemetry/FenixProductDetector.kt | 49 +
.../mozilla/focus/telemetry/GleanMetricsService.kt | 243 +
.../org/mozilla/focus/telemetry/MetricsService.kt | 18 +
.../focus/telemetry/ProfilerMarkerFactProcessor.kt | 85 +
.../mozilla/focus/telemetry/TelemetryMiddleware.kt | 148 +
.../startuptelemetry/AppStartReasonProvider.kt | 99 +
.../DefaultActivityLifecycleCallbacks.kt | 25 +
.../startuptelemetry/StartupActivityLog.kt | 106 +
.../startuptelemetry/StartupPathProvider.kt | 98 +
.../startuptelemetry/StartupStateProvider.kt | 137 +
.../startuptelemetry/StartupTypeTelemetry.kt | 110 +
.../src/main/java/org/mozilla/focus/theme/Theme.kt | 14 +
.../focus/topsites/DefaultTopSitesStorage.kt | 64 +
.../mozilla/focus/topsites/DefaultTopSitesView.kt | 22 +
.../mozilla/focus/topsites/RenameTopSiteDialog.kt | 47 +
.../java/org/mozilla/focus/topsites/TopSites.kt | 166 +
.../org/mozilla/focus/topsites/TopSitesOverlay.kt | 112 +
.../org/mozilla/focus/ui/dialog/FocusDialog.kt | 209 +
.../mozilla/focus/ui/menu/CustomDropdownMenu.kt | 46 +
.../java/org/mozilla/focus/ui/menu/MenuItem.kt | 16 +
.../java/org/mozilla/focus/ui/theme/FocusColors.kt | 49 +
.../org/mozilla/focus/ui/theme/FocusDimensions.kt | 15 +
.../java/org/mozilla/focus/ui/theme/FocusTheme.kt | 138 +
.../org/mozilla/focus/ui/theme/FocusTypography.kt | 136 +
.../java/org/mozilla/focus/utils/AppConstants.kt | 33 +
.../mozilla/focus/utils/ClickableSubstringLink.kt | 112 +
.../main/java/org/mozilla/focus/utils/Features.kt | 15 +
.../java/org/mozilla/focus/utils/FocusSnackbar.kt | 146 +
.../mozilla/focus/utils/FocusSnackbarDelegate.kt | 37 +
.../java/org/mozilla/focus/utils/HtmlLoader.kt | 121 +
.../java/org/mozilla/focus/utils/IntentUtils.kt | 82 +
.../focus/utils/OneShotOnPreDrawListener.kt | 27 +
.../java/org/mozilla/focus/utils/SearchUtils.kt | 19 +
.../main/java/org/mozilla/focus/utils/Settings.kt | 507 ++
.../java/org/mozilla/focus/utils/SupportUtils.kt | 133 +
.../main/java/org/mozilla/focus/utils/ViewUtils.kt | 103 +
.../org/mozilla/focus/widget/AboutPreference.kt | 24 +
.../org/mozilla/focus/widget/CookiesPreference.kt | 46 +
.../focus/widget/DefaultBrowserPreference.kt | 90 +
.../mozilla/focus/widget/LocaleListPreference.java | 0
.../org/mozilla/focus/widget/MozillaPreference.kt | 21 +
.../widget/ResizableKeyboardCoordinatorLayout.kt | 34 +
.../focus/widget/ResizableKeyboardLinearLayout.kt | 29 +
.../focus/widget/ResizableKeyboardViewDelegate.kt | 119 +
.../mozilla/focus/widget/SwitchWithDescription.kt | 72 +
.../focus/widget/TelemetrySwitchPreference.kt | 44 +
.../app/src/main/res/anim/erase_animation.xml | 20 +
.../app/src/main/res/anim/fab_reveal.xml | 16 +
.../app/src/main/res/anim/fade_in.xml | 9 +
.../app/src/main/res/anim/fade_out.xml | 9 +
.../src/main/res/color/preference_title_text.xml | 9 +
.../res/color/selected_search_engine_state.xml | 9 +
.../main/res/drawable-hdpi/focus_search_widget.png | Bin 0 -> 17294 bytes
.../focus_search_widget_promote_dialog.png | Bin 0 -> 29857 bytes
.../drawable-hdpi/focus_snackbar_background.xml | 9 +
.../src/main/res/drawable-hdpi/onboarding_img1.png | Bin 0 -> 49768 bytes
.../src/main/res/drawable-hdpi/onboarding_img2.png | Bin 0 -> 24031 bytes
.../src/main/res/drawable-hdpi/onboarding_img3.png | Bin 0 -> 15524 bytes
.../src/main/res/drawable-hdpi/onboarding_img4.png | Bin 0 -> 23796 bytes
.../res/drawable-land-night/home_background.xml | 31 +
.../src/main/res/drawable-land/dark_background.xml | 46 +
.../src/main/res/drawable-land/home_background.xml | 31 +
.../drawable-night-hdpi/focus_search_widget.png | Bin 0 -> 16899 bytes
.../focus_search_widget_promote_dialog.png | Bin 0 -> 29279 bytes
.../main/res/drawable-night/home_background.xml | 31 +
.../res/drawable-nodpi/ic_homescreen_shape.png | Bin 0 -> 1283 bytes
.../src/main/res/drawable-v24/ic_splash_screen.xml | 213 +
.../main/res/drawable-xhdpi/onboarding_img1.png | Bin 0 -> 32761 bytes
.../main/res/drawable-xhdpi/onboarding_img2.png | Bin 0 -> 17156 bytes
.../main/res/drawable-xhdpi/onboarding_img3.png | Bin 0 -> 11388 bytes
.../main/res/drawable-xhdpi/onboarding_img4.png | Bin 0 -> 31166 bytes
.../main/res/drawable-xxhdpi/onboarding_img1.png | Bin 0 -> 106485 bytes
.../main/res/drawable-xxhdpi/onboarding_img2.png | Bin 0 -> 49218 bytes
.../main/res/drawable-xxhdpi/onboarding_img3.png | Bin 0 -> 30170 bytes
.../main/res/drawable-xxhdpi/onboarding_img4.png | Bin 0 -> 50377 bytes
.../src/main/res/drawable/background_gradient.xml | 11 +
.../res/drawable/background_install_banner.xml | 8 +
.../background_list_item_current_session.xml | 8 +
.../res/drawable/background_list_item_session.xml | 8 +
.../main/res/drawable/background_open_in_item.xml | 12 +
.../background_search_suggestion_section.xml | 12 +
.../src/main/res/drawable/background_snackbar.xml | 19 +
.../context_menu_navigation_view_background.xml | 9 +
.../app/src/main/res/drawable/dark_background.xml | 43 +
.../src/main/res/drawable/dialog_background.xml | 7 +
.../res/drawable/dialog_warning_background.xml | 10 +
.../main/res/drawable/find_in_page_background.xml | 18 +
.../res/drawable/firstrun_button_background.xml | 15 +
.../res/drawable/foreground_list_item_erase.xml | 7 +
.../app/src/main/res/drawable/highlight_dot.xml | 10 +
.../app/src/main/res/drawable/home_background.xml | 32 +
.../src/main/res/drawable/ic_arrowhead_down.xml | 13 +
.../app/src/main/res/drawable/ic_arrowhead_up.xml | 13 +
.../src/main/res/drawable/ic_autoplay_enabled.xml | 17 +
.../app/src/main/res/drawable/ic_back_button.xml | 14 +
.../src/main/res/drawable/ic_camera_enabled.xml | 14 +
.../app/src/main/res/drawable/ic_check.xml | 14 +
.../src/main/res/drawable/ic_cookies_disable.xml | 13 +
.../app/src/main/res/drawable/ic_developer.xml | 12 +
.../app/src/main/res/drawable/ic_download.xml | 16 +
.../main/res/drawable/ic_error_session_crashed.xml | 387 ++
.../app/src/main/res/drawable/ic_favorite.xml | 13 +
.../app/src/main/res/drawable/ic_fingerprint.xml | 14 +
.../app/src/main/res/drawable/ic_firefox.xml | 13 +
.../app/src/main/res/drawable/ic_info.xml | 13 +
.../app/src/main/res/drawable/ic_internet.xml | 13 +
.../app/src/main/res/drawable/ic_language.xml | 13 +
.../main/res/drawable/ic_launcher_background.xml | 13 +
.../main/res/drawable/ic_launcher_foreground.xml | 213 +
.../app/src/main/res/drawable/ic_link.xml | 22 +
.../app/src/main/res/drawable/ic_menu.xml | 13 +
.../app/src/main/res/drawable/ic_mozilla.xml | 13 +
.../app/src/main/res/drawable/ic_notification.xml | 13 +
.../app/src/main/res/drawable/ic_reorder.xml | 13 +
.../src/main/res/drawable/ic_shortcut_erase.xml | 16 +
.../app/src/main/res/drawable/ic_splash_screen.png | Bin 0 -> 5499 bytes
.../app/src/main/res/drawable/ic_tab_new.xml | 14 +
.../src/main/res/drawable/indicator_onboarding.xml | 12 +
.../res/drawable/indicator_onboarding_default.xml | 15 +
.../res/drawable/indicator_onboarding_selected.xml | 15 +
.../res/drawable/menu_item_dark_background.xml | 8 +
.../src/main/res/drawable/mozac_ic_broken_lock.xml | 19 +
.../app/src/main/res/drawable/onboarding_logo.xml | 214 +
.../res/drawable/onboarding_second_screen_icon.png | Bin 0 -> 39339 bytes
.../src/main/res/drawable/photon_progressbar.xml | 21 +
.../drawable/preference_foreground_disabled.xml | 7 +
...erence_multiselect_search_engine_foreground.xml | 10 +
.../app/src/main/res/drawable/scrollbar_thumb.xml | 8 +
.../src/main/res/drawable/tab_number_border.xml | 16 +
.../main/res/drawable/toolbar_url_background.xml | 8 +
...top_rounded_corners_bottom_sheet_background.xml | 11 +
.../src/main/res/drawable/urlbar_background.xml | 7 +
.../app/src/main/res/drawable/wordmark2.xml | 232 +
.../app/src/main/res/font/metropolis.xml | 81 +
.../app/src/main/res/font/metropolis_black.ttf | Bin 0 -> 74628 bytes
.../src/main/res/font/metropolis_blackitalic.ttf | Bin 0 -> 76524 bytes
.../app/src/main/res/font/metropolis_bold.ttf | Bin 0 -> 75660 bytes
.../src/main/res/font/metropolis_bolditalic.ttf | Bin 0 -> 78120 bytes
.../app/src/main/res/font/metropolis_extrabold.ttf | Bin 0 -> 75980 bytes
.../main/res/font/metropolis_extrabolditalic.ttf | Bin 0 -> 78284 bytes
.../main/res/font/metropolis_extralighitalic.ttf | Bin 0 -> 75624 bytes
.../src/main/res/font/metropolis_extralight.ttf | Bin 0 -> 74496 bytes
.../app/src/main/res/font/metropolis_light.ttf | Bin 0 -> 74456 bytes
.../src/main/res/font/metropolis_lightitalic.ttf | Bin 0 -> 75692 bytes
.../app/src/main/res/font/metropolis_medium.ttf | Bin 0 -> 75476 bytes
.../src/main/res/font/metropolis_mediumitalic.ttf | Bin 0 -> 77588 bytes
.../app/src/main/res/font/metropolis_regular.ttf | Bin 0 -> 76504 bytes
.../src/main/res/font/metropolis_regularitalic.ttf | Bin 0 -> 76748 bytes
.../app/src/main/res/font/metropolis_semibold.ttf | Bin 0 -> 76016 bytes
.../main/res/font/metropolis_semibolditalic.ttf | Bin 0 -> 78180 bytes
.../app/src/main/res/font/metropolis_thin.ttf | Bin 0 -> 73292 bytes
.../src/main/res/font/metropolis_thinitalic.ttf | Bin 0 -> 74688 bytes
.../app/src/main/res/layout/active_study_item.xml | 40 +
.../app/src/main/res/layout/activity_customtab.xml | 12 +
.../app/src/main/res/layout/activity_info.xml | 19 +
.../app/src/main/res/layout/activity_main.xml | 30 +
.../app/src/main/res/layout/connection_details.xml | 89 +
.../res/layout/cookie_banner_reducer_details.xml | 97 +
.../app/src/main/res/layout/cookies_preference.xml | 55 +
.../src/main/res/layout/custom_tab_menu_item.xml | 21 +
.../main/res/layout/dialog_add_to_homescreen2.xml | 113 +
.../res/layout/dialog_full_screen_notification.xml | 38 +
.../layout/dialog_tracking_protection_sheet.xml | 152 +
.../app/src/main/res/layout/firstrun_page.xml | 98 +
.../app/src/main/res/layout/focus_preference.xml | 79 +
.../layout/focus_preference_category_no_title.xml | 8 +
.../res/layout/focus_preference_compose_layout.xml | 11 +
.../res/layout/focus_preference_left_checkbox.xml | 49 +
.../main/res/layout/focus_preference_new_tab.xml | 73 +
.../main/res/layout/focus_preference_no_icon.xml | 44 +
.../app/src/main/res/layout/focus_snackbar.xml | 65 +
.../app/src/main/res/layout/fragment_about.xml | 18 +
.../main/res/layout/fragment_about_libraries.xml | 18 +
.../layout/fragment_autocomplete_add_domain.xml | 44 +
.../layout/fragment_autocomplete_customdomains.xml | 12 +
.../app/src/main/res/layout/fragment_browser.xml | 93 +
.../main/res/layout/fragment_crash_reporter.xml | 85 +
.../res/layout/fragment_exceptions_domains.xml | 38 +
.../app/src/main/res/layout/fragment_firstrun.xml | 49 +
.../app/src/main/res/layout/fragment_info.xml | 19 +
.../app/src/main/res/layout/fragment_open_with.xml | 24 +
.../res/layout/fragment_search_suggestions.xml | 126 +
.../app/src/main/res/layout/fragment_settings.xml | 12 +
.../app/src/main/res/layout/fragment_studies.xml | 57 +
.../app/src/main/res/layout/fragment_urlinput.xml | 117 +
.../src/main/res/layout/item_add_custom_domain.xml | 15 +
.../app/src/main/res/layout/item_app.xml | 40 +
.../app/src/main/res/layout/item_custom_domain.xml | 41 +
.../main/res/layout/item_indicator_menu_button.xml | 25 +
.../src/main/res/layout/item_install_banner.xml | 41 +
.../app/src/main/res/layout/item_session.xml | 47 +
.../app/src/main/res/layout/menu_item.xml | 40 +
.../app/src/main/res/layout/menu_navigation.xml | 38 +
.../app/src/main/res/layout/popup_tabs.xml | 27 +
.../main/res/layout/preference_default_browser.xml | 14 +
.../layout/preference_manual_add_search_engine.xml | 56 +
.../main/res/layout/preference_radio_button.xml | 57 +
.../res/layout/preference_screen_header_layout.xml | 13 +
.../layout/preference_search_engine_chooser.xml | 16 +
.../layout/preference_section_header_layout.xml | 17 +
.../res/layout/preference_switch_learn_more.xml | 64 +
.../res/layout/search_engine_checkbox_button.xml | 17 +
.../main/res/layout/search_engine_radio_button.xml | 18 +
.../src/main/res/layout/studies_section_item.xml | 27 +
.../main/res/layout/switch_with_description.xml | 54 +
.../app/src/main/res/layout/toolbar.xml | 9 +
.../src/main/res/menu/menu_autocomplete_add.xml | 10 +
.../src/main/res/menu/menu_autocomplete_list.xml | 10 +
.../src/main/res/menu/menu_autocomplete_remove.xml | 12 +
.../app/src/main/res/menu/menu_exceptions_list.xml | 10 +
.../main/res/menu/menu_remove_search_engines.xml | 12 +
.../res/menu/menu_search_engine_manual_add.xml | 19 +
.../app/src/main/res/menu/menu_search_engines.xml | 15 +
.../src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 9 +
.../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4076 bytes
.../src/main/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6336 bytes
.../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2137 bytes
.../src/main/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3400 bytes
.../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5622 bytes
.../main/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 8345 bytes
.../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 9985 bytes
.../main/res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 14316 bytes
.../src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 14478 bytes
.../main/res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 20937 bytes
.../focus-android/app/src/main/res/raw/about.html | 55 +
.../focus-android/app/src/main/res/raw/gpl.html | 713 ++
.../app/src/main/res/raw/initial_experiments.json | 3 +
.../app/src/main/res/raw/licenses.html | 948 +++
.../focus-android/app/src/main/res/raw/rights.html | 56 +
.../app/src/main/res/transition/firstrun_exit.xml | 7 +
.../app/src/main/res/values-ace/strings.xml | 784 +++
.../app/src/main/res/values-af/strings.xml | 246 +
.../app/src/main/res/values-am/strings.xml | 1096 +++
.../app/src/main/res/values-an/strings.xml | 311 +
.../app/src/main/res/values-anp/strings.xml | 730 ++
.../app/src/main/res/values-ar/strings.xml | 839 +++
.../app/src/main/res/values-ast/strings.xml | 490 ++
.../app/src/main/res/values-ay/strings.xml | 476 ++
.../app/src/main/res/values-az/strings.xml | 403 ++
.../app/src/main/res/values-be/strings.xml | 1086 +++
.../app/src/main/res/values-bg/strings.xml | 1103 +++
.../app/src/main/res/values-bn/strings.xml | 403 ++
.../app/src/main/res/values-bo/strings.xml | 328 +
.../app/src/main/res/values-bs/strings.xml | 1102 +++
.../app/src/main/res/values-ca/strings.xml | 1095 +++
.../app/src/main/res/values-cak/strings.xml | 1030 +++
.../app/src/main/res/values-co/strings.xml | 1125 +++
.../app/src/main/res/values-cs/strings.xml | 1113 +++
.../app/src/main/res/values-cy/strings.xml | 1122 +++
.../app/src/main/res/values-da/strings.xml | 1122 +++
.../app/src/main/res/values-de/strings.xml | 1120 +++
.../app/src/main/res/values-dsb/strings.xml | 1119 +++
.../app/src/main/res/values-el/strings.xml | 1128 +++
.../app/src/main/res/values-en-rCA/strings.xml | 1118 +++
.../app/src/main/res/values-en-rGB/strings.xml | 1090 +++
.../app/src/main/res/values-eo/strings.xml | 1118 +++
.../app/src/main/res/values-es-rAR/strings.xml | 1121 +++
.../app/src/main/res/values-es-rCL/strings.xml | 1117 +++
.../app/src/main/res/values-es-rES/strings.xml | 1117 +++
.../app/src/main/res/values-es-rMX/strings.xml | 1111 +++
.../app/src/main/res/values-et/strings.xml | 881 +++
.../app/src/main/res/values-eu/strings.xml | 1117 +++
.../app/src/main/res/values-fa/strings.xml | 857 +++
.../app/src/main/res/values-fi/strings.xml | 1119 +++
.../app/src/main/res/values-fr/strings.xml | 1120 +++
.../app/src/main/res/values-fur/strings.xml | 1080 +++
.../app/src/main/res/values-fy-rNL/strings.xml | 1121 +++
.../app/src/main/res/values-ga-rIE/strings.xml | 257 +
.../app/src/main/res/values-gl/strings.xml | 1098 +++
.../app/src/main/res/values-gu-rIN/strings.xml | 403 ++
.../app/src/main/res/values-hi-rIN/strings.xml | 749 ++
.../app/src/main/res/values-hr/strings.xml | 1035 +++
.../app/src/main/res/values-hsb/strings.xml | 1119 +++
.../app/src/main/res/values-hu/strings.xml | 1121 +++
.../app/src/main/res/values-hus/strings.xml | 361 +
.../app/src/main/res/values-hy-rAM/strings.xml | 634 ++
.../app/src/main/res/values-ia/strings.xml | 1123 +++
.../app/src/main/res/values-in/strings.xml | 1108 +++
.../app/src/main/res/values-is/strings.xml | 1091 +++
.../app/src/main/res/values-it/strings.xml | 1122 +++
.../app/src/main/res/values-iw/strings.xml | 1093 +++
.../app/src/main/res/values-ixl/strings.xml | 1103 +++
.../app/src/main/res/values-ja/strings.xml | 1120 +++
.../app/src/main/res/values-jv/strings.xml | 152 +
.../app/src/main/res/values-ka/strings.xml | 1116 +++
.../app/src/main/res/values-kaa/strings.xml | 795 +++
.../app/src/main/res/values-kab/strings.xml | 1123 +++
.../app/src/main/res/values-kk/strings.xml | 1117 +++
.../app/src/main/res/values-ko/strings.xml | 1117 +++
.../app/src/main/res/values-kw/strings.xml | 1097 +++
.../app/src/main/res/values-lo/strings.xml | 1121 +++
.../app/src/main/res/values-lt/strings.xml | 841 +++
.../app/src/main/res/values-meh/strings.xml | 1105 +++
.../app/src/main/res/values-mix/strings.xml | 1072 +++
.../app/src/main/res/values-mr/strings.xml | 680 ++
.../app/src/main/res/values-ms/strings.xml | 403 ++
.../app/src/main/res/values-my/strings.xml | 634 ++
.../app/src/main/res/values-nb-rNO/strings.xml | 1117 +++
.../app/src/main/res/values-ne-rNP/strings.xml | 574 ++
.../app/src/main/res/values-night/colors.xml | 146 +
.../app/src/main/res/values-nl/strings.xml | 1121 +++
.../app/src/main/res/values-nn-rNO/strings.xml | 1127 +++
.../app/src/main/res/values-nv/strings.xml | 32 +
.../app/src/main/res/values-oc/strings.xml | 1114 +++
.../app/src/main/res/values-pa-rIN/strings.xml | 1118 +++
.../app/src/main/res/values-pai/strings.xml | 354 +
.../app/src/main/res/values-pl/strings.xml | 1119 +++
.../app/src/main/res/values-ppl/strings.xml | 1072 +++
.../app/src/main/res/values-pt-rBR/strings.xml | 1122 +++
.../app/src/main/res/values-quc/strings.xml | 709 ++
.../app/src/main/res/values-quy/strings.xml | 302 +
.../app/src/main/res/values-ro/strings.xml | 661 ++
.../app/src/main/res/values-ru/strings.xml | 1122 +++
.../app/src/main/res/values-si/strings.xml | 1078 +++
.../app/src/main/res/values-sk/strings.xml | 1121 +++
.../app/src/main/res/values-skr/strings.xml | 1107 +++
.../app/src/main/res/values-sl/strings.xml | 1123 +++
.../app/src/main/res/values-sn/strings.xml | 403 ++
.../app/src/main/res/values-sq/strings.xml | 1127 +++
.../app/src/main/res/values-sr/strings.xml | 1114 +++
.../app/src/main/res/values-su/strings.xml | 1115 +++
.../app/src/main/res/values-sv-rSE/strings.xml | 1121 +++
.../app/src/main/res/values-sw480dp/dimens.xml | 9 +
.../app/src/main/res/values-sw600dp/dimens.xml | 9 +
.../app/src/main/res/values-ta/strings.xml | 552 ++
.../app/src/main/res/values-te/strings.xml | 666 ++
.../app/src/main/res/values-tg/strings.xml | 1086 +++
.../app/src/main/res/values-th/strings.xml | 1119 +++
.../app/src/main/res/values-tr/strings.xml | 1127 +++
.../app/src/main/res/values-trs/strings.xml | 1090 +++
.../app/src/main/res/values-tsz/strings.xml | 947 +++
.../app/src/main/res/values-tt/strings.xml | 1103 +++
.../app/src/main/res/values-uk/strings.xml | 1120 +++
.../app/src/main/res/values-ur/strings.xml | 818 +++
.../app/src/main/res/values-vi/strings.xml | 1120 +++
.../app/src/main/res/values-wo/strings.xml | 241 +
.../app/src/main/res/values-yua/strings.xml | 479 ++
.../app/src/main/res/values-zam/strings.xml | 443 ++
.../app/src/main/res/values-zh-rCN/strings.xml | 1127 +++
.../app/src/main/res/values-zh-rHK/strings.xml | 933 +++
.../app/src/main/res/values-zh-rTW/strings.xml | 1121 +++
.../focus-android/app/src/main/res/values/app.xml | 13 +
.../app/src/main/res/values/attrs.xml | 39 +
.../app/src/main/res/values/colors.xml | 152 +
.../app/src/main/res/values/configuration.xml | 14 +
.../app/src/main/res/values/dimens.xml | 91 +
.../app/src/main/res/values/fonts.xml | 8 +
.../focus-android/app/src/main/res/values/ids.xml | 21 +
.../app/src/main/res/values/preference_keys.xml | 125 +
.../app/src/main/res/values/static_strings.xml | 26 +
.../app/src/main/res/values/strings.xml | 1069 +++
.../app/src/main/res/values/strings_references.xml | 35 +
.../app/src/main/res/values/styles.xml | 326 +
.../app/src/main/res/xml/advanced_settings.xml | 33 +
.../app/src/main/res/xml/autocomplete.xml | 32 +
.../src/main/res/xml/cookie_banner_settings.xml | 13 +
.../app/src/main/res/xml/experiments_settings.xml | 11 +
.../app/src/main/res/xml/general_settings.xml | 42 +
.../src/main/res/xml/manual_add_search_engine.xml | 10 +
.../app/src/main/res/xml/mozilla_settings.xml | 44 +
.../src/main/res/xml/privacy_security_settings.xml | 159 +
.../app/src/main/res/xml/provider_paths.xml | 9 +
.../app/src/main/res/xml/remove_search_engines.xml | 9 +
.../src/main/res/xml/search_engine_settings.xml | 23 +
.../app/src/main/res/xml/search_settings.xml | 25 +
.../app/src/main/res/xml/search_widget_info.xml | 15 +
.../app/src/main/res/xml/secret_settings.xml | 17 +
.../app/src/main/res/xml/settings.xml | 44 +
.../app/src/main/res/xml/site_permissions.xml | 50 +
.../app/src/nightly/ic_launcher-playstore.png | Bin 0 -> 71476 bytes
.../java/org/mozilla/focus/utils/AdjustHelper.java | 14 +
.../nightly/java/org/mozilla/focus/web/Config.kt | 10 +
.../nightly/res/drawable-land/dark_background.xml | 28 +
.../nightly/res/drawable-v24/ic_splash_screen.xml | 253 +
.../src/nightly/res/drawable/dark_background.xml | 28 +
.../res/drawable/ic_launcher_background.xml | 18 +
.../res/drawable/ic_launcher_foreground.xml | 253 +
.../src/nightly/res/drawable/ic_splash_screen.png | Bin 0 -> 5669 bytes
.../src/nightly/res/drawable/onboarding_logo.xml | 248 +
.../app/src/nightly/res/drawable/wordmark2.xml | 281 +
.../nightly/res/mipmap-anydpi-v26/ic_launcher.xml | 8 +
.../src/nightly/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4230 bytes
.../nightly/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6819 bytes
.../src/nightly/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2521 bytes
.../nightly/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3997 bytes
.../src/nightly/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6134 bytes
.../nightly/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 10083 bytes
.../src/nightly/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 10218 bytes
.../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 16745 bytes
.../src/nightly/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 15031 bytes
.../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 24870 bytes
.../app/src/nightly/res/values-night/colors.xml | 7 +
.../java/org/mozilla/focus/BrowserFragmentTest.kt | 108 +
.../java/org/mozilla/focus/TestFocusApplication.kt | 95 +
.../animation/TransitionDrawableGroupTest.java | 42 +
.../BiometricAuthenticationFragmentTest.kt | 70 +
.../integration/BrowserToolbarIntegrationTest.kt | 234 +
.../integration/FindInPageIntegrationTest.kt | 46 +
.../integration/FullScreenIntegrationTest.kt | 374 +
.../integration/InputToolbarIntegrationTest.kt | 82 +
.../org/mozilla/focus/cfr/CfrMiddlewareTest.kt | 127 +
.../focus/contextmenu/ContextMenuCandidatesTest.kt | 81 +
.../mozilla/focus/experiments/NimbusSetupTest.kt | 25 +
.../org/mozilla/focus/ext/BrowserToolbarTest.kt | 80 +
.../test/java/org/mozilla/focus/ext/StringTest.kt | 80 +
.../src/test/java/org/mozilla/focus/ext/UriTest.kt | 82 +
.../java/org/mozilla/focus/locale/LocalesTest.kt | 46 +
.../focus/menu/BrowserMenuControllerTest.kt | 162 +
.../focus/onboarding/OnboardingControllerTest.kt | 72 +
.../focus/onboarding/OnboardingStorageTest.kt | 79 +
.../SearchSuggestionsViewModelTest.kt | 59 +
.../searchwidget/ExternalIntentNavigationTest.kt | 203 +
.../focus/searchwidget/SearchWidgetProviderTest.kt | 65 +
.../focus/settings/SearchEngineValidationTest.kt | 94 +
.../org/mozilla/focus/shortcut/HomeScreenTest.kt | 20 +
.../mozilla/focus/shortcut/IconGeneratorTest.kt | 59 +
.../SitePermissionOptionsStorageTest.kt | 342 +
.../SitePermissionOptionsStoreTest.kt | 89 +
.../sitepermissions/SitePermissionsFragmentTest.kt | 57 +
.../focus/telemetry/GleanMetricsServiceTest.kt | 42 +
.../telemetry/ProfilerMarkerFactProcessorTest.kt | 96 +
.../focus/telemetry/StartupActivityLogTest.kt | 99 +
.../focus/telemetry/StartupPathProviderTest.kt | 213 +
.../focus/telemetry/StartupStateProviderTest.kt | 417 ++
.../focus/telemetry/StartupTypeTelemetryTest.kt | 152 +
.../focus/topsites/DefaultTopSitesStorageTest.kt | 136 +
.../org/mozilla/focus/utils/IntentUtilsTest.kt | 21 +
.../org/mozilla/focus/utils/SupportUtilsTest.kt | 64 +
.../org.mockito.plugins.MockMaker | 2 +
.../app/src/test/resources/robolectric.properties | 3 +
mobile/android/focus-android/app/tags.yaml | 175 +
.../androidTest/copy-robo-crash-artifacts.py | 273 +
.../taskcluster/androidTest/flank-arm-beta.yml | 36 +
.../androidTest/flank-arm-start-test-robo.yml | 27 +
.../androidTest/flank-arm-start-test.yml | 36 +
.../taskcluster/androidTest/flank-arm64-v8a.yml | 36 +
.../taskcluster/androidTest/flank-x86.yml | 36 +
.../androidTest/parse-ui-test-fromfile.py | 97 +
.../taskcluster/androidTest/parse-ui-test.py | 89 +
.../taskcluster/androidTest/robo-test.sh | 101 +
.../automation/taskcluster/androidTest/ui-test.sh | 147 +
mobile/android/focus-android/build.gradle | 181 +
mobile/android/focus-android/codecov.yml | 22 +
mobile/android/focus-android/docs/Adjust-Usage.md | 66 +
.../focus-android/docs/Architecture-Decisions.md | 92 +
.../focus-android/docs/Battery-Debugging.md | 37 +
.../android/focus-android/docs/Content-blocking.md | 58 +
.../docs/Crash-Reporting-with-Sentry.md | 147 +
.../docs/Development-Custom-GeckoView.md | 90 +
.../focus-android/docs/Feature-&-Issue-workflow.md | 54 +
.../focus-android/docs/GeckoView-(In-Progress).md | 6 +
mobile/android/focus-android/docs/Home.md | 40 +
.../android/focus-android/docs/Homescreen-Tips.md | 35 +
.../docs/Multisession-architecture.md | 19 +
.../docs/Recommended-pre-push-hook.md | 30 +
.../android/focus-android/docs/Release-Process.md | 58 +
.../android/focus-android/docs/Release-tracks.md | 53 +
.../android/focus-android/docs/Removing-strings.md | 29 +
.../android/focus-android/docs/Sprint-Process.md | 47 +
mobile/android/focus-android/docs/Telemetry.md | 345 +
mobile/android/focus-android/docs/UI-Test.md | 54 +
mobile/android/focus-android/docs/index.rst | 28 +
.../docs/l10n-Screenshot-Generation.md | 29 +
mobile/android/focus-android/gradle.properties | 33 +
.../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43453 bytes
.../gradle/wrapper/gradle-wrapper.properties | 7 +
mobile/android/focus-android/gradlew | 249 +
mobile/android/focus-android/gradlew.bat | 92 +
mobile/android/focus-android/l10n.toml | 118 +
.../plugins/focusdependencies/build.gradle | 25 +
.../plugins/focusdependencies/settings.gradle | 19 +
.../src/main/java/FocusDependenciesPlugin.kt | 68 +
.../android/focus-android/quality/checkstyle.xml | 45 +
.../focus-android/quality/detekt-baseline.xml | 406 ++
mobile/android/focus-android/quality/detekt.yml | 789 +++
.../android/focus-android/quality/license.template | 3 +
mobile/android/focus-android/quality/pmd-rules.xml | 42 +
.../focus-android/quality/pre-push-recommended.sh | 25 +
.../focus-android/quality/spotbugs-exclude.xml | 29 +
mobile/android/focus-android/settings.gradle | 136 +
.../focus-android/tools/data_renewal_generate.py | 189 +
.../focus-android/tools/data_renewal_request.py | 55 +
.../android/focus-android/tools/docker/Dockerfile | 78 +
.../tools/docker/licenses/android-sdk-license | 2 +
.../docker/licenses/android-sdk-preview-license | 2 +
.../focus-android/tools/gradle/versionCode.gradle | 45 +
.../focus-android/tools/update-glean-tags.py | 58 +
852 files changed, 165284 insertions(+)
create mode 100644 mobile/android/focus-android/.buildconfig.yml
create mode 100644 mobile/android/focus-android/.editorconfig
create mode 100644 mobile/android/focus-android/CODEOWNERS
create mode 100644 mobile/android/focus-android/CONTRIBUTING.md
create mode 100644 mobile/android/focus-android/README.md
create mode 100644 mobile/android/focus-android/Screengrabfile
create mode 100644 mobile/android/focus-android/app/.experimenter.yaml
create mode 100644 mobile/android/focus-android/app/.gitignore
create mode 100644 mobile/android/focus-android/app/build.gradle
create mode 100644 mobile/android/focus-android/app/lint-baseline.xml
create mode 100644 mobile/android/focus-android/app/lint.xml
create mode 100644 mobile/android/focus-android/app/metrics.yaml
create mode 100644 mobile/android/focus-android/app/nimbus.fml.yaml
create mode 100644 mobile/android/focus-android/app/pings.yaml
create mode 100644 mobile/android/focus-android/app/proguard-rules.pro
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/audioPage.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/cross-site-cookies.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/download.jpg
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/etpPages/adsTrackers.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/etpPages/analyticsTrackers.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/etpPages/otherTrackers.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/etpPages/socialTrackers.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/genericPage.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/global_privacy_control.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/htmlControls.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/image_test.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/mutedVideoPage.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/rabbit.jpg
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/resources/audioSample.mp3
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/resources/clip.mp4
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/same-site-cookies.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/service-worker.js
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/storage_check.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/storage_start.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/tab1.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/tab2.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/tab3.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/test.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/assets/videoPage.html
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/AddToHomescreenTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ContextMenusTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/CustomTabTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/DownloadFileTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EnhancedTrackingProtectionSettingsTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EraseBrowsingDataTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ErrorPagesTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/FirstRunTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MediaPlaybackTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MozillaSupportPagesTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MultitaskingTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OldFirstRunTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OpenInExternalBrowserDialogueTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SafeBrowsingTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SearchTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsAdvancedTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsGeneralTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsPrivacyTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ShortcutsTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SitePermissionsTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SwitchContextTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ThreeDotMainMenuTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/URLAutocompleteTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/AddToHomeScreenRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/CustomTabRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/DownloadRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/HomeScreenRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/NotificationRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SearchRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsAdvancedMenuRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsGeneralMenuRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsMozillaMenuRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsPrivacyMenuRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSearchMenuRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSitePermissionsRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SiteSecurityInfoSheetRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/TabsTrayRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/ThreeDotMainMenuRobot.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/Constants.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/DeleteFilesHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/EspressoHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/FeatureSettingsHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/HostScreencapScreenshotStrategy.java
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MainActivityTestRule.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockLocationUpdatesRule.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockWebServerHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/RetryTestRule.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/StringsHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestAssetHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestHelper.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/ext/WaitNotNull.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/RecyclerViewIdlingResource.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/SessionLoadedIdlingResource.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/GlobalPrivacyControlTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/LocalSessionStorageTest.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/AllowListScreenshots.java
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/BrowserScreenScreenshots.java
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ErrorPagesScreenshots.java
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/FirstRunScreenshots.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/HomeScreenScreenshots.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/NotificationScreenshots.java
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ScreenshotTest.java
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/SettingsScreenshots.kt
create mode 100644 mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/testAnnotations/SmokeTest.kt
create mode 100644 mobile/android/focus-android/app/src/beta/res/drawable-v24/ic_splash_screen.xml
create mode 100644 mobile/android/focus-android/app/src/beta/res/drawable/ic_splash_screen.png
create mode 100644 mobile/android/focus-android/app/src/beta/res/values-night/colors.xml
create mode 100644 mobile/android/focus-android/app/src/debug/AndroidManifest.xml
create mode 100644 mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/DebugFocusApplication.kt
create mode 100644 mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/utils/AdjustHelper.java
create mode 100644 mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/web/Config.kt
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/ic_launcher-playstore.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/java/org/mozilla/focus/utils/AdjustHelper.java
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable-land/dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/ic_launcher_foreground.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/icon_foreground.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable/dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable/ic_launcher_background.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable/onboarding_logo.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/drawable/wordmark2.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher_round.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/values/app.xml
create mode 100644 mobile/android/focus-android/app/src/focusBeta/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/focusDebug/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/focusNightly/res/values/app.xml
create mode 100644 mobile/android/focus-android/app/src/focusNightly/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/focusRelease/AndroidManifest.xml
create mode 100644 mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/utils/AdjustHelper.java
create mode 100644 mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/web/Config.kt
create mode 100644 mobile/android/focus-android/app/src/focusRelease/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/klar/res/drawable/background_gradient_dark.xml
create mode 100644 mobile/android/focus-android/app/src/klar/res/drawable/wordmark2.xml
create mode 100644 mobile/android/focus-android/app/src/klar/res/values/app.xml
create mode 100644 mobile/android/focus-android/app/src/klarBeta/java/org/mozilla/focus/utils/AdjustHelper.java
create mode 100644 mobile/android/focus-android/app/src/klarBeta/res/drawable/onboarding_logo.xml
create mode 100644 mobile/android/focus-android/app/src/klarBeta/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/klarDebug/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/drawable-v24/icon_foreground.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/drawable/background_gradient_dark.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/drawable/icon_background.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/drawable/toolbar_url_background.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher_round.xml
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/klarNightly/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/utils/AdjustHelper.java
create mode 100644 mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/web/Config.kt
create mode 100644 mobile/android/focus-android/app/src/klarRelease/res/xml-v25/shortcuts.xml
create mode 100644 mobile/android/focus-android/app/src/main/AndroidManifest.xml
create mode 100644 mobile/android/focus-android/app/src/main/assets/error_style.css
create mode 100644 mobile/android/focus-android/app/src/main/assets/style.css
create mode 100644 mobile/android/focus-android/app/src/main/ic_launcher-playstore.png
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/Components.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/FocusApplication.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CrashListActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CustomTabActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseAndOpenShortcutActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseShortcutActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/InstallFirefoxActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/IntentReceiverActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/MainActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/TextActionActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/animation/TransitionDrawableGroup.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewStep.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewUtils.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteAddFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteCustomDomainsPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDefaultDomainsPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDomainFormatter.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteListFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteRemoveFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/LockObserver.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/BlockedTrackersMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/LocalizedContent.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserMenuController.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FindInPageIntegration.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/NavigationButtonsIntegration.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cfr/CfrMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/components/EngineProvider.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/contextmenu/ContextMenuCandidates.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerOption.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerRejectAllPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerExceptionDetailsSwitch.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerDetailsPanel.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerItem.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStatus.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStore.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/DefaultCookieBannerReducerInteractor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/customtabs/CustomTabsService.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/downloads/DownloadService.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/AppContentInterceptor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/ClientWrapper.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/EngineSharedPreferencesListener.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/SanityCheckMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsListFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsRemoveFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/experiments/NimbusSetup.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Activity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/AndroidViewModel.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserStore.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserToolbar.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/ContentState.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Context.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Fragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/PreferenceFragmentCompat.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/SessionState.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/String.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Uri.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunCardView.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunPagerAdapter.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/AddToHomescreenDialogFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BaseFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/CrashReporterFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/FirstrunFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/UrlInputFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/AboutFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/SecretSettingsUnlocker.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingController.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingInteractor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStep.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStorage.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/input/InputToolbarIntegration.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/LocaleManager.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/Locales.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/DefaultLanguageScreenInteractor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/Language.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageListItem.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageScreenStore.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageStorage.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleDescriptor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleFragmentCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/media/MediaSessionService.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/ToolbarMenu.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/CustomTabMenu.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/DefaultBrowserMenu.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenu.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenuItem.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/MainActivityNavigation.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/Navigator.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/StoreLink.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppAdapter.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppViewHolder.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/InstallBannerViewHolder.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/OpenWithFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/perf/Performance.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/ManualAddSearchEnginePreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/MultiselectSearchEngineListPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/RadioSearchEngineListPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEngineListPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEnginePreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchFilterMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchMigration.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsPreferences.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModel.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchOverlay.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/ExternalIntentNavigation.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetProvider.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetUtils.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/VoiceSearchActivity.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/IntentProcessor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/PrivateNotificationFeature.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/SessionNotificationService.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/VisibilityLifeCycleCallback.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabViewHolder.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsAdapter.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsPopup.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/AboutLibrariesFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseComposeFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsLikeFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/GeneralSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/HttpsOnlyModePreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/InstalledSearchEnginesSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/LearnMoreSwitchPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/ManualAddSearchEngineSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/MozillaSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RadioButtonPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RemoveSearchEnginesSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SafeBrowsingSwitchPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SearchSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/StatePreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/AdvancedSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SecretSettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SharedPreferenceUpdater.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionOption.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/DefaultSitePermissionOptionsScreenInteractor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/HardwarePermissionCheckFeature.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermission.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionListItem.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsScreenStore.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorage.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorageMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/ConnectionDetailsPanel.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceSwitch.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceToolTipCompose.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PrivacySecuritySettingsFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/TrackingProtectionPanel.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesAdapter.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesFragment.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesListItem.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesRecyclerView.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewHolder.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewModel.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/HomeScreen.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/IconGenerator.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppAction.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppReducer.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppState.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppStore.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ActivationPing.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/BrowsersCache.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FactsProcessor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FenixProductDetector.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/GleanMetricsService.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/MetricsService.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessor.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/TelemetryMiddleware.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/AppStartReasonProvider.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/DefaultActivityLifecycleCallbacks.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupActivityLog.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupPathProvider.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupStateProvider.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupTypeTelemetry.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/theme/Theme.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesStorage.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesView.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/RenameTopSiteDialog.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSites.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSitesOverlay.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/dialog/FocusDialog.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/CustomDropdownMenu.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/MenuItem.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusColors.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusDimensions.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTheme.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTypography.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/AppConstants.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ClickableSubstringLink.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Features.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbar.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbarDelegate.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/HtmlLoader.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/IntentUtils.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/OneShotOnPreDrawListener.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SearchUtils.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Settings.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SupportUtils.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ViewUtils.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/AboutPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/CookiesPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/DefaultBrowserPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/LocaleListPreference.java
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/MozillaPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardCoordinatorLayout.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardLinearLayout.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardViewDelegate.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/SwitchWithDescription.kt
create mode 100644 mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/TelemetrySwitchPreference.kt
create mode 100644 mobile/android/focus-android/app/src/main/res/anim/erase_animation.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/anim/fab_reveal.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/anim/fade_in.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/anim/fade_out.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/color/preference_title_text.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/color/selected_search_engine_state.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget_promote_dialog.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_snackbar_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img1.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img2.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img3.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img4.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-land-night/home_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-land/dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-land/home_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget_promote_dialog.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-night/home_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-nodpi/ic_homescreen_shape.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-v24/ic_splash_screen.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img1.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img2.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img3.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img4.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img1.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img2.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img3.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img4.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_gradient.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_install_banner.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_list_item_current_session.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_list_item_session.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_open_in_item.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_search_suggestion_section.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/background_snackbar.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/context_menu_navigation_view_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/dialog_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/dialog_warning_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/find_in_page_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/firstrun_button_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/foreground_list_item_erase.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/highlight_dot.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/home_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_down.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_up.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_autoplay_enabled.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_back_button.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_camera_enabled.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_check.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_cookies_disable.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_developer.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_download.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_error_session_crashed.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_favorite.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_fingerprint.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_firefox.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_info.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_internet.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_language.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_foreground.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_link.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_menu.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_mozilla.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_notification.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_reorder.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_shortcut_erase.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_splash_screen.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/ic_tab_new.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_default.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_selected.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/menu_item_dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/mozac_ic_broken_lock.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/onboarding_logo.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/onboarding_second_screen_icon.png
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/photon_progressbar.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/preference_foreground_disabled.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/preference_multiselect_search_engine_foreground.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/scrollbar_thumb.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/tab_number_border.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/toolbar_url_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/top_rounded_corners_bottom_sheet_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/urlbar_background.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/drawable/wordmark2.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_black.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_blackitalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_bold.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_bolditalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_extrabold.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_extrabolditalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_extralighitalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_extralight.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_light.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_lightitalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_medium.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_mediumitalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_regular.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_regularitalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_semibold.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_semibolditalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_thin.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/font/metropolis_thinitalic.ttf
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/active_study_item.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/activity_customtab.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/activity_info.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/activity_main.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/connection_details.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/cookie_banner_reducer_details.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/cookies_preference.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/custom_tab_menu_item.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/dialog_add_to_homescreen2.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/dialog_tracking_protection_sheet.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/firstrun_page.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_preference.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_preference_category_no_title.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_preference_compose_layout.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_preference_left_checkbox.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_preference_new_tab.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_preference_no_icon.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/focus_snackbar.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_about.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_about_libraries.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_add_domain.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_customdomains.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_browser.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_crash_reporter.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_exceptions_domains.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_firstrun.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_info.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_open_with.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_search_suggestions.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_studies.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/fragment_urlinput.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/item_add_custom_domain.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/item_app.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/item_custom_domain.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/item_indicator_menu_button.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/item_install_banner.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/item_session.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/menu_item.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/menu_navigation.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/popup_tabs.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_default_browser.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_manual_add_search_engine.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_radio_button.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_screen_header_layout.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_search_engine_chooser.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_section_header_layout.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/preference_switch_learn_more.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/search_engine_checkbox_button.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/search_engine_radio_button.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/studies_section_item.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/switch_with_description.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/layout/toolbar.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_add.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_list.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_remove.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_exceptions_list.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_remove_search_engines.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_search_engine_manual_add.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/menu/menu_search_engines.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/main/res/raw/about.html
create mode 100644 mobile/android/focus-android/app/src/main/res/raw/gpl.html
create mode 100644 mobile/android/focus-android/app/src/main/res/raw/initial_experiments.json
create mode 100644 mobile/android/focus-android/app/src/main/res/raw/licenses.html
create mode 100644 mobile/android/focus-android/app/src/main/res/raw/rights.html
create mode 100644 mobile/android/focus-android/app/src/main/res/transition/firstrun_exit.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ace/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-af/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-am/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-an/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-anp/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ar/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ast/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ay/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-az/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-be/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-bg/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-bn/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-bo/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-bs/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ca/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-cak/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-co/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-cs/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-cy/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-da/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-de/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-dsb/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-el/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-en-rCA/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-en-rGB/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-eo/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-es-rAR/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-es-rCL/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-es-rES/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-es-rMX/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-et/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-eu/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-fa/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-fi/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-fr/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-fur/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-fy-rNL/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ga-rIE/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-gl/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-gu-rIN/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-hi-rIN/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-hr/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-hsb/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-hu/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-hus/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-hy-rAM/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ia/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-in/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-is/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-it/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-iw/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ixl/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ja/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-jv/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ka/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-kaa/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-kab/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-kk/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ko/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-kw/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-lo/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-lt/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-meh/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-mix/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-mr/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ms/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-my/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-nb-rNO/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ne-rNP/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-night/colors.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-nl/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-nn-rNO/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-nv/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-oc/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-pa-rIN/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-pai/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-pl/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ppl/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-pt-rBR/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-quc/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-quy/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ro/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ru/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-si/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sk/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-skr/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sl/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sn/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sq/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sr/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-su/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sv-rSE/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sw480dp/dimens.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-sw600dp/dimens.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ta/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-te/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-tg/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-th/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-tr/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-trs/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-tsz/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-tt/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-uk/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-ur/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-vi/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-wo/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-yua/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-zam/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-zh-rCN/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-zh-rHK/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values-zh-rTW/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/app.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/attrs.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/colors.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/configuration.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/dimens.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/fonts.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/ids.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/preference_keys.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/static_strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/strings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/strings_references.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/values/styles.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/advanced_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/autocomplete.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/cookie_banner_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/experiments_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/general_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/manual_add_search_engine.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/mozilla_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/privacy_security_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/provider_paths.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/remove_search_engines.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/search_engine_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/search_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/search_widget_info.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/secret_settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/settings.xml
create mode 100644 mobile/android/focus-android/app/src/main/res/xml/site_permissions.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/ic_launcher-playstore.png
create mode 100644 mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/utils/AdjustHelper.java
create mode 100644 mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/web/Config.kt
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable-land/dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable-v24/ic_splash_screen.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable/dark_background.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_background.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_foreground.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable/ic_splash_screen.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable/onboarding_logo.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/drawable/wordmark2.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png
create mode 100644 mobile/android/focus-android/app/src/nightly/res/values-night/colors.xml
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/BrowserFragmentTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/animation/TransitionDrawableGroupTest.java
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegrationTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FindInPageIntegrationTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/InputToolbarIntegrationTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/cfr/CfrMiddlewareTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/contextmenu/ContextMenuCandidatesTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/experiments/NimbusSetupTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/BrowserToolbarTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/StringTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/UriTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/locale/LocalesTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/menu/BrowserMenuControllerTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingControllerTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingStorageTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModelTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/ExternalIntentNavigationTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/SearchWidgetProviderTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/settings/SearchEngineValidationTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/HomeScreenTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/IconGeneratorTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStorageTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStoreTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionsFragmentTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/GleanMetricsServiceTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessorTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupActivityLogTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupPathProviderTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupStateProviderTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupTypeTelemetryTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/topsites/DefaultTopSitesStorageTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/IntentUtilsTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/SupportUtilsTest.kt
create mode 100644 mobile/android/focus-android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
create mode 100644 mobile/android/focus-android/app/src/test/resources/robolectric.properties
create mode 100644 mobile/android/focus-android/app/tags.yaml
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/copy-robo-crash-artifacts.py
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-beta.yml
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test-robo.yml
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test.yml
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm64-v8a.yml
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/flank-x86.yml
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test-fromfile.py
create mode 100644 mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test.py
create mode 100755 mobile/android/focus-android/automation/taskcluster/androidTest/robo-test.sh
create mode 100755 mobile/android/focus-android/automation/taskcluster/androidTest/ui-test.sh
create mode 100644 mobile/android/focus-android/build.gradle
create mode 100644 mobile/android/focus-android/codecov.yml
create mode 100644 mobile/android/focus-android/docs/Adjust-Usage.md
create mode 100644 mobile/android/focus-android/docs/Architecture-Decisions.md
create mode 100644 mobile/android/focus-android/docs/Battery-Debugging.md
create mode 100644 mobile/android/focus-android/docs/Content-blocking.md
create mode 100644 mobile/android/focus-android/docs/Crash-Reporting-with-Sentry.md
create mode 100644 mobile/android/focus-android/docs/Development-Custom-GeckoView.md
create mode 100644 mobile/android/focus-android/docs/Feature-&-Issue-workflow.md
create mode 100644 mobile/android/focus-android/docs/GeckoView-(In-Progress).md
create mode 100644 mobile/android/focus-android/docs/Home.md
create mode 100644 mobile/android/focus-android/docs/Homescreen-Tips.md
create mode 100644 mobile/android/focus-android/docs/Multisession-architecture.md
create mode 100644 mobile/android/focus-android/docs/Recommended-pre-push-hook.md
create mode 100644 mobile/android/focus-android/docs/Release-Process.md
create mode 100644 mobile/android/focus-android/docs/Release-tracks.md
create mode 100644 mobile/android/focus-android/docs/Removing-strings.md
create mode 100644 mobile/android/focus-android/docs/Sprint-Process.md
create mode 100644 mobile/android/focus-android/docs/Telemetry.md
create mode 100644 mobile/android/focus-android/docs/UI-Test.md
create mode 100644 mobile/android/focus-android/docs/index.rst
create mode 100644 mobile/android/focus-android/docs/l10n-Screenshot-Generation.md
create mode 100644 mobile/android/focus-android/gradle.properties
create mode 100644 mobile/android/focus-android/gradle/wrapper/gradle-wrapper.jar
create mode 100644 mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties
create mode 100755 mobile/android/focus-android/gradlew
create mode 100644 mobile/android/focus-android/gradlew.bat
create mode 100644 mobile/android/focus-android/l10n.toml
create mode 100644 mobile/android/focus-android/plugins/focusdependencies/build.gradle
create mode 100644 mobile/android/focus-android/plugins/focusdependencies/settings.gradle
create mode 100644 mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt
create mode 100644 mobile/android/focus-android/quality/checkstyle.xml
create mode 100644 mobile/android/focus-android/quality/detekt-baseline.xml
create mode 100644 mobile/android/focus-android/quality/detekt.yml
create mode 100644 mobile/android/focus-android/quality/license.template
create mode 100644 mobile/android/focus-android/quality/pmd-rules.xml
create mode 100755 mobile/android/focus-android/quality/pre-push-recommended.sh
create mode 100644 mobile/android/focus-android/quality/spotbugs-exclude.xml
create mode 100644 mobile/android/focus-android/settings.gradle
create mode 100755 mobile/android/focus-android/tools/data_renewal_generate.py
create mode 100755 mobile/android/focus-android/tools/data_renewal_request.py
create mode 100644 mobile/android/focus-android/tools/docker/Dockerfile
create mode 100644 mobile/android/focus-android/tools/docker/licenses/android-sdk-license
create mode 100644 mobile/android/focus-android/tools/docker/licenses/android-sdk-preview-license
create mode 100644 mobile/android/focus-android/tools/gradle/versionCode.gradle
create mode 100755 mobile/android/focus-android/tools/update-glean-tags.py
(limited to 'mobile/android/focus-android')
diff --git a/mobile/android/focus-android/.buildconfig.yml b/mobile/android/focus-android/.buildconfig.yml
new file mode 100644
index 0000000000..6b6e2c7fad
--- /dev/null
+++ b/mobile/android/focus-android/.buildconfig.yml
@@ -0,0 +1,165 @@
+projects:
+ app:
+ upstream_dependencies:
+ - browser-domains
+ - browser-engine-gecko
+ - browser-errorpages
+ - browser-icons
+ - browser-menu
+ - browser-menu2
+ - browser-session-storage
+ - browser-state
+ - browser-storage-sync
+ - browser-tabstray
+ - browser-thumbnails
+ - browser-toolbar
+ - compose-awesomebar
+ - compose-cfr
+ - concept-awesomebar
+ - concept-base
+ - concept-engine
+ - concept-fetch
+ - concept-menu
+ - concept-storage
+ - concept-sync
+ - concept-tabstray
+ - concept-toolbar
+ - feature-app-links
+ - feature-awesomebar
+ - feature-contextmenu
+ - feature-customtabs
+ - feature-downloads
+ - feature-findinpage
+ - feature-intent
+ - feature-media
+ - feature-prompts
+ - feature-search
+ - feature-session
+ - feature-sitepermissions
+ - feature-tabs
+ - feature-toolbar
+ - feature-top-sites
+ - feature-webcompat
+ - feature-webcompat-reporter
+ - lib-auth
+ - lib-crash
+ - lib-crash-sentry
+ - lib-fetch-okhttp
+ - lib-publicsuffixlist
+ - lib-state
+ - service-digitalassetlinks
+ - service-glean
+ - service-location
+ - service-nimbus
+ - support-base
+ - support-images
+ - support-ktx
+ - support-license
+ - support-locale
+ - support-remotesettings
+ - support-rusthttp
+ - support-rustlog
+ - support-test
+ - support-test-libstate
+ - support-utils
+ - support-webextensions
+ - tooling-lint
+ - ui-autocomplete
+ - ui-colors
+ - ui-icons
+ - ui-tabcounter
+ - ui-widgets
+variants:
+- apks:
+ - abi: arm64-v8a
+ fileName: app-focus-arm64-v8a-debug.apk
+ - abi: armeabi-v7a
+ fileName: app-focus-armeabi-v7a-debug.apk
+ - abi: x86
+ fileName: app-focus-x86-debug.apk
+ - abi: x86_64
+ fileName: app-focus-x86_64-debug.apk
+ build_type: debug
+ name: focusDebug
+- apks:
+ - abi: arm64-v8a
+ fileName: app-klar-arm64-v8a-debug.apk
+ - abi: armeabi-v7a
+ fileName: app-klar-armeabi-v7a-debug.apk
+ - abi: x86
+ fileName: app-klar-x86-debug.apk
+ - abi: x86_64
+ fileName: app-klar-x86_64-debug.apk
+ build_type: debug
+ name: klarDebug
+- apks:
+ - abi: arm64-v8a
+ fileName: app-focus-arm64-v8a-release-unsigned.apk
+ - abi: armeabi-v7a
+ fileName: app-focus-armeabi-v7a-release-unsigned.apk
+ - abi: x86
+ fileName: app-focus-x86-release-unsigned.apk
+ - abi: x86_64
+ fileName: app-focus-x86_64-release-unsigned.apk
+ build_type: release
+ name: focusRelease
+- apks:
+ - abi: arm64-v8a
+ fileName: app-klar-arm64-v8a-release-unsigned.apk
+ - abi: armeabi-v7a
+ fileName: app-klar-armeabi-v7a-release-unsigned.apk
+ - abi: x86
+ fileName: app-klar-x86-release-unsigned.apk
+ - abi: x86_64
+ fileName: app-klar-x86_64-release-unsigned.apk
+ build_type: release
+ name: klarRelease
+- apks:
+ - abi: arm64-v8a
+ fileName: app-focus-arm64-v8a-beta-unsigned.apk
+ - abi: armeabi-v7a
+ fileName: app-focus-armeabi-v7a-beta-unsigned.apk
+ - abi: x86
+ fileName: app-focus-x86-beta-unsigned.apk
+ - abi: x86_64
+ fileName: app-focus-x86_64-beta-unsigned.apk
+ build_type: beta
+ name: focusBeta
+- apks:
+ - abi: arm64-v8a
+ fileName: app-klar-arm64-v8a-beta-unsigned.apk
+ - abi: armeabi-v7a
+ fileName: app-klar-armeabi-v7a-beta-unsigned.apk
+ - abi: x86
+ fileName: app-klar-x86-beta-unsigned.apk
+ - abi: x86_64
+ fileName: app-klar-x86_64-beta-unsigned.apk
+ build_type: beta
+ name: klarBeta
+- apks:
+ - abi: arm64-v8a
+ fileName: app-focus-arm64-v8a-nightly-unsigned.apk
+ - abi: armeabi-v7a
+ fileName: app-focus-armeabi-v7a-nightly-unsigned.apk
+ - abi: x86
+ fileName: app-focus-x86-nightly-unsigned.apk
+ - abi: x86_64
+ fileName: app-focus-x86_64-nightly-unsigned.apk
+ build_type: nightly
+ name: focusNightly
+- apks:
+ - abi: arm64-v8a
+ fileName: app-klar-arm64-v8a-nightly-unsigned.apk
+ - abi: armeabi-v7a
+ fileName: app-klar-armeabi-v7a-nightly-unsigned.apk
+ - abi: x86
+ fileName: app-klar-x86-nightly-unsigned.apk
+ - abi: x86_64
+ fileName: app-klar-x86_64-nightly-unsigned.apk
+ build_type: nightly
+ name: klarNightly
+- apks:
+ - abi: noarch
+ fileName: app-debug-androidTest.apk
+ build_type: androidTest
+ name: androidTest
diff --git a/mobile/android/focus-android/.editorconfig b/mobile/android/focus-android/.editorconfig
new file mode 100644
index 0000000000..3232ddd4a3
--- /dev/null
+++ b/mobile/android/focus-android/.editorconfig
@@ -0,0 +1,5 @@
+[*.{kt,kts}]
+ij_kotlin_allow_trailing_comma_on_call_site=true
+ij_kotlin_allow_trailing_comma=true
+
+ktlint_standard_filename = disabled
\ No newline at end of file
diff --git a/mobile/android/focus-android/CODEOWNERS b/mobile/android/focus-android/CODEOWNERS
new file mode 100644
index 0000000000..cedf4a9cf8
--- /dev/null
+++ b/mobile/android/focus-android/CODEOWNERS
@@ -0,0 +1,5 @@
+# These owners will be the default owners for everything in
+# the repo. Unless a later match takes precedence,
+# @global-owner1 and @global-owner2 will be requested for
+# review when someone opens a pull request.
+* @mozilla-mobile/focus-codeowners
diff --git a/mobile/android/focus-android/CONTRIBUTING.md b/mobile/android/focus-android/CONTRIBUTING.md
new file mode 100644
index 0000000000..597b7bc1d7
--- /dev/null
+++ b/mobile/android/focus-android/CONTRIBUTING.md
@@ -0,0 +1,4 @@
+# Contributing to Focus for Android
+
+Please see our guidelines in our shared-docs repo:
+https://github.com/mozilla-mobile/shared-docs/blob/main/android/CONTRIBUTING.md
diff --git a/mobile/android/focus-android/README.md b/mobile/android/focus-android/README.md
new file mode 100644
index 0000000000..c3ca39d2fe
--- /dev/null
+++ b/mobile/android/focus-android/README.md
@@ -0,0 +1,119 @@
+# Firefox Focus for Android
+
+_Browse like no one’s watching. The new Firefox Focus automatically blocks a wide range of online trackers — from the moment you launch it to the second you leave it. Easily erase your history, passwords and cookies, so you won’t get followed by things like unwanted ads._
+
+Firefox Focus provides automatic ad blocking and tracking protection on an easy-to-use private browser.
+
+
+
+* [Google Play: Firefox Focus (Global)](https://play.google.com/store/apps/details?id=org.mozilla.focus)
+* [Google Play: Firefox Klar (Germany, Austria & Switzerland)](https://play.google.com/store/apps/details?id=org.mozilla.klar)
+* [Download APKs](https://github.com/mozilla-mobile/focus-android/releases)
+
+
+
+## Getting Involved
+
+
+We encourage you to participate in this open source project. We love Pull Requests, Bug Reports, ideas, (security) code reviews or any other kind of positive contribution.
+
+Before you attempt to make a contribution please read the [Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/).
+
+* [Guide to Contributing](https://github.com/mozilla-mobile/shared-docs/blob/main/android/CONTRIBUTING.md) (**New contributors start here!**)
+
+* [View current Issues](https://github.com/mozilla-mobile/focus-android/issues), [view current Pull Requests](https://github.com/mozilla-mobile/focus-android/pulls), or [file a security issue][sec issue].
+
+* Opt-in to our Mailing List [firefox-focus-public@](https://mail.mozilla.org/listinfo/firefox-focus-public) to keep up to date.
+
+* [View the Wiki](https://github.com/mozilla-mobile/focus-android/wiki).
+
+**Beginners!** - Watch out for [Issues with the "Good First Issue" label](https://github.com/mozilla-mobile/focus-android/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). These are easy bugs that have been left for first timers to have a go, get involved and make a positive contribution to the project!
+
+## Build Instructions
+
+
+1. Clone or Download the repository:
+
+ ```shell
+ git clone https://github.com/mozilla-mobile/focus-android
+ ```
+
+2. Import the project into Android Studio **or** build on the command line:
+
+ ```shell
+ ./gradlew clean app:assembleFocusDebug
+ ```
+
+3. Make sure to select the correct build variant in Android Studio:
+**focusArmDebug** for ARM
+**focusX86Debug** for X86
+**focusAarch64Debug** for ARM64
+
+## local.properties helpers
+You can speed up or enhance local development by setting a few helper flags available in `local.properties` which will be made easily available as gradle properties.
+
+### Automatically sign release builds
+To sign your release builds with your debug key automatically, add the following to `/local.properties`:
+
+```sh
+autosignReleaseWithDebugKey
+```
+
+With this line, release build variants will automatically be signed with your debug key (like debug builds), allowing them to be built and installed directly through Android Studio or the command line.
+
+This is helpful when you're building release variants frequently, for example to test feature flags and or do performance analyses.
+
+### Building debuggable release variants
+
+Nightly, Beta and Release variants are getting published to Google Play and therefore are not debuggable. To locally create debuggable builds of those variants, add the following to `/local.properties`:
+
+```sh
+debuggable
+```
+
+### Auto-publication workflow for application-services and glean
+If you're making changes to these projects and want to test them in Focus, auto-publication workflow is the fastest, most reliable
+way to do that.
+
+In `local.properties`, specify a relative path to your local `glean` and/or `application-services` projects. E.g.:
+- `autoPublish.glean.dir=../glean`
+- `autoPublish.application-services.dir=../application-services`
+
+Once these flags are set, your Focus builds will include any local modifications present in these projects.
+
+See a [demo of auto-publication workflow in action](https://www.youtube.com/watch?v=qZKlBzVvQGc).
+
+## Pre-push hooks
+To reduce review turn-around time, we'd like all pushes to run tests locally. We'd
+recommend you use our provided pre-push hook in `quality/pre-push-recommended.sh`.
+Using this hook will guarantee your hook gets updated as the repository changes.
+This hook tries to run as much as possible without taking too much time.
+
+To add it, run this command from the project root:
+```sh
+ln -s ../../quality/pre-push-recommended.sh .git/hooks/pre-push
+```
+
+To push without running the pre-push hook (e.g. doc updates):
+```sh
+git push --no-verify
+```
+
+## Test Channel on Google PlayStore
+To get Focus Nightly on your device, follow these steps:
+
+1) Visit https://groups.google.com/g/firefox-focus-pre-release and join the Google Group
+2) After you have joined the group opt-in to receive Nightly builds, again with the same Google account: https://play.google.com/apps/testing/org.mozilla.focus.nightly
+3) Download Firefox Focus (Nightly) from Google Play: https://play.google.com/store/apps/details?id=org.mozilla.focus.nightly
+
+Make sure you use the same Google Account for both steps.
+
+
+## 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
+ file, You can obtain one at http://mozilla.org/MPL/2.0/
+
+[sec issue]: https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&bug_file_loc=http%3A%2F%2F&bug_ignored=0&bug_severity=normal&bug_status=NEW&cf_fx_iteration=---&cf_fx_points=---&component=Security%3A%20Android&contenttypemethod=autodetect&contenttypeselection=text%2Fplain&defined_groups=1&flag_type-4=X&flag_type-607=X&flag_type-791=X&flag_type-800=X&flag_type-803=X&form_name=enter_bug&groups=firefox-core-security&maketemplate=Remember%20values%20as%20bookmarkable%20template&op_sys=Unspecified&priority=--&product=Focus&rep_platform=Unspecified&target_milestone=---&version=---
diff --git a/mobile/android/focus-android/Screengrabfile b/mobile/android/focus-android/Screengrabfile
new file mode 100644
index 0000000000..fd85f68dba
--- /dev/null
+++ b/mobile/android/focus-android/Screengrabfile
@@ -0,0 +1,22 @@
+# 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/.
+
+# This is the template for our Screengrabfile used in automation.
+# tools/taskcluster/generate_screengrab_config.py will read this
+# file and generate the final configuration that we use inside
+# a taskcluster task.
+
+app_package_name 'org.mozilla.focus.debug'
+use_tests_in_packages ['org.mozilla.focus.screenshots']
+
+app_apk_path('~/focus-android/focus-android/app/build/outputs/apk/focus/debug/app-focus-x86-debug.apk')
+tests_apk_path('/focus-android/focus-android/app/build/outputs/apk/androidTest/focus/debug/app-focus-debug-androidTest.apk')
+
+locales(['en-US', 'fr-FR', 'it-IT', 'de-DE', 'ja', 'ru', 'zh-CN', 'zh-TW', 'ko'])
+
+# Clear all previous screenshots locally. Technically not needed in automation.
+# But it's easier to debug this on a local device if there are no old screenshots
+# hanging around.
+clear_previous_screenshots true
+reinstall_app true
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/.experimenter.yaml b/mobile/android/focus-android/app/.experimenter.yaml
new file mode 100644
index 0000000000..fa6a1d67f4
--- /dev/null
+++ b/mobile/android/focus-android/app/.experimenter.yaml
@@ -0,0 +1,23 @@
+---
+cookie-banner:
+ description: Nimbus feature name intended to control the cookie banner handling in the app.
+ hasExposure: true
+ exposureDescription: ""
+ variables:
+ is-cookie-handling-enabled:
+ type: boolean
+ description: "If 'true' , the app will show the settings part for cookie banner handling"
+onboarding:
+ description: Nimbus feature name intended to control the onboarding plus all CFRs in the app.
+ hasExposure: true
+ exposureDescription: ""
+ variables:
+ is-cfr-enabled:
+ type: boolean
+ description: "If `true`, the app will show the cfrs"
+ is-enabled:
+ type: boolean
+ description: "If `true`, the app will show the new onboarding screen"
+ is-promote-search-widget-dialog-enabled:
+ type: boolean
+ description: "If `true`, the app will show the new dialog for promote search widget"
diff --git a/mobile/android/focus-android/app/.gitignore b/mobile/android/focus-android/app/.gitignore
new file mode 100644
index 0000000000..211d729623
--- /dev/null
+++ b/mobile/android/focus-android/app/.gitignore
@@ -0,0 +1,3 @@
+/build
+
+src/main/java/org/mozilla/focus/generated/
diff --git a/mobile/android/focus-android/app/build.gradle b/mobile/android/focus-android/app/build.gradle
new file mode 100644
index 0000000000..3a82291ce9
--- /dev/null
+++ b/mobile/android/focus-android/app/build.gradle
@@ -0,0 +1,788 @@
+plugins {
+ id "com.jetbrains.python.envs" version "$python_envs_plugin"
+}
+
+if (findProject(":geckoview") != null) {
+ buildDir "${topobjdir}/gradle/build/mobile/android/focus-android"
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-parcelize'
+apply plugin: 'jacoco'
+apply plugin: 'com.google.android.gms.oss-licenses-plugin'
+
+def versionCodeGradle = "$project.rootDir/tools/gradle/versionCode.gradle"
+if (findProject(":geckoview") != null) {
+ versionCodeGradle = "$project.rootDir/mobile/android/focus-android/tools/gradle/versionCode.gradle"
+}
+apply from: versionCodeGradle
+
+if (findProject(":geckoview") != null) {
+ apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"
+}
+
+import com.android.build.api.variant.FilterConfiguration
+import groovy.json.JsonOutput
+import org.gradle.internal.logging.text.StyledTextOutput.Style
+import org.gradle.internal.logging.text.StyledTextOutputFactory
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.tasks.testing.TestResult.ResultType
+
+android {
+ if (project.hasProperty("testBuildType")) {
+ // Allowing to configure the test build type via command line flag (./gradlew -PtestBuildType=beta ..)
+ // in order to run UI tests against other build variants than debug in automation.
+ testBuildType project.property("testBuildType")
+ }
+
+ defaultConfig {
+ applicationId "org.mozilla"
+ minSdkVersion config.minSdkVersion
+ compileSdk config.compileSdkVersion
+ targetSdkVersion config.targetSdkVersion
+ versionCode 11 // This versionCode is "frozen" for local builds. For "release" builds we
+ // override this with a generated versionCode at build time.
+ // The versionName is dynamically overridden for all the build variants at build time.
+ versionName Config.generateDebugVersionName()
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunnerArguments clearPackageData: 'true'
+ // See override in release builds for why it's blank.
+ buildConfigField "String", "VCS_HASH", "\"\""
+
+ vectorDrawables.useSupportLibrary = true
+ }
+
+ bundle {
+ language {
+ // Because we have runtime language selection we will keep all strings and languages
+ // in the base APKs.
+ enableSplit = false
+ }
+ }
+
+ lint {
+ lintConfig file("lint.xml")
+ baseline file("lint-baseline.xml")
+ }
+
+ // We have a three dimensional build configuration:
+ // BUILD TYPE (debug, release) X PRODUCT FLAVOR (focus, klar)
+
+ buildTypes {
+ release {
+ // We allow disabling optimization by passing `-PdisableOptimization` to gradle. This is used
+ // in automation for UI testing non-debug builds.
+ shrinkResources !project.hasProperty("disableOptimization")
+ minifyEnabled !project.hasProperty("disableOptimization")
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ matchingFallbacks = ['release']
+ buildConfigField "String", "VCS_HASH", "\"${Config.getVcsHash()}\""
+
+ if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
+ println ("All builds will be automatically signed with the debug key")
+ signingConfig signingConfigs.debug
+ }
+
+ if (gradle.hasProperty("localProperties.debuggable")) {
+ println ("All builds will be debuggable")
+ debuggable true
+ }
+ }
+ debug {
+ applicationIdSuffix ".debug"
+ matchingFallbacks = ['debug']
+ }
+ beta {
+ initWith release
+ applicationIdSuffix ".beta"
+ // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
+ manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_focus_beta"]
+ }
+ nightly {
+ initWith release
+ applicationIdSuffix ".nightly"
+ // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
+ manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_focus_nightly"]
+ }
+ }
+ testOptions {
+ execution 'ANDROIDX_TEST_ORCHESTRATOR'
+ animationsDisabled = true
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
+
+ buildFeatures {
+ compose true
+ viewBinding true
+ buildConfig true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = Versions.compose_compiler
+ }
+
+ if (findProject(":geckoview") != null) {
+ project.configureProductFlavors.delegate = it
+ project.configureProductFlavors()
+ }
+
+ flavorDimensions.add("product")
+
+ productFlavors {
+ // In most countries we are Firefox Focus - but in some we need to be Firefox Klar
+ focus {
+ dimension "product"
+
+ applicationIdSuffix ".focus"
+
+ // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
+ manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_focus"]
+ }
+ klar {
+ dimension "product"
+
+ applicationIdSuffix ".klar"
+
+ // This is used when the user selects text in other third-party apps. See https://github.com/mozilla-mobile/focus-android/issues/6478
+ manifestPlaceholders = [textSelectionSearchAction: "@string/text_selection_search_action_klar"]
+ }
+ }
+
+ splits {
+ abi {
+ enable true
+
+ reset()
+
+ include "x86", "armeabi-v7a", "arm64-v8a", "x86_64"
+ }
+ }
+
+ sourceSets {
+ test {
+ resources {
+ // Make the default asset folder available as test resource folder. Robolectric seems
+ // to fail to read assets for our setup. With this we can just read the files directly
+ // and do not need to rely on Robolectric.
+ srcDir "${projectDir}/src/main/assets/"
+ }
+ }
+
+ if (findProject(":geckoview") != null) {
+ // Release
+ withGeckoBinariesFocusRelease.root = 'src/focusRelease'
+ withGeckoBinariesKlarRelease.root = 'src/klarRelease'
+ withoutGeckoBinariesFocusRelease.root = 'src/focusRelease'
+ withoutGeckoBinariesKlarRelease.root = 'src/klarRelease'
+
+ // Debug
+ withGeckoBinariesFocusDebug.root = 'src/focusDebug'
+ withGeckoBinariesKlarDebug.root = 'src/klarDebug'
+ withoutGeckoBinariesFocusDebug.root = 'src/focusDebug'
+ withoutGeckoBinariesKlarDebug.root = 'src/klarDebug'
+
+ // Nightly
+ withGeckoBinariesFocusNightly.root = 'src/focusNightly'
+ withGeckoBinariesKlarNightly.root = 'src/klarNightly'
+ withoutGeckoBinariesFocusNightly.root = 'src/focusNightly'
+ withoutGeckoBinariesKlarNightly.root = 'src/klarNightly'
+ } else {
+ // Release
+ focusRelease.root = 'src/focusRelease'
+ klarRelease.root = 'src/klarRelease'
+
+ // Debug
+ focusDebug.root = 'src/focusDebug'
+ klarDebug.root = 'src/klarDebug'
+
+ // Nightly
+ focusNightly.root = 'src/focusNightly'
+ klarNightly.root = 'src/klarNightly'
+ }
+ }
+ packagingOptions {
+ resources {
+ pickFirsts += ['META-INF/atomicfu.kotlin_module', 'META-INF/proguard/coroutines.pro']
+ }
+ jniLibs {
+ useLegacyPackaging true
+ }
+ }
+
+ namespace 'org.mozilla.focus'
+}
+
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions.allWarningsAsErrors = true
+ kotlinOptions.freeCompilerArgs += [
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ "-opt-in=kotlin.RequiresOptIn",
+ "-Xjvm-default=all"
+ ]
+}
+
+// -------------------------------------------------------------------------------------------------
+// Generate Kotlin code for the Focus Glean metrics.
+// -------------------------------------------------------------------------------------------------
+apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
+apply plugin: "org.mozilla.appservices.nimbus-gradle-plugin"
+
+nimbus {
+ // The path to the Nimbus feature manifest file
+ manifestFile = "nimbus.fml.yaml"
+ // Map from the variant name to the channel as experimenter and nimbus understand it.
+ // If nimbus's channels were accurately set up well for this project, then this
+ // shouldn't be needed.
+ channels = [
+ focusDebug: "debug",
+ focusNightly: "nightly",
+ focusBeta: "beta",
+ focusRelease: "release",
+ klarDebug: "debug",
+ klarNightly: "nightly",
+ klarBeta: "beta",
+ klarRelease: "release",
+ withGeckoBinariesFocusDebug: "debug",
+ withGeckoBinariesFocusNightly: "nightly",
+ withGeckoBinariesFocusBeta: "beta",
+ withGeckoBinariesFocusRelease: "release",
+ withGeckoBinariesKlarDebug: "debug",
+ withGeckoBinariesKlarNightly: "nightly",
+ withGeckoBinariesKlarBeta: "beta",
+ withGeckoBinariesKlarRelease: "release",
+ withoutGeckoBinariesFocusDebug: "debug",
+ withoutGeckoBinariesFocusNightly: "nightly",
+ withoutGeckoBinariesFocusBeta: "beta",
+ withoutGeckoBinariesFocusRelease: "release",
+ withoutGeckoBinariesKlarDebug: "debug",
+ withoutGeckoBinariesKlarNightly: "nightly",
+ withoutGeckoBinariesKlarBeta: "beta",
+ withoutGeckoBinariesKlarRelease: "release",
+ ]
+ // This is generated by the FML and should be checked into git.
+ // It will be fetched by Experimenter (the Nimbus experiment website)
+ // and used to inform experiment configuration.
+ experimenterManifest = ".experimenter.yaml"
+}
+
+dependencies {
+ implementation platform(ComponentsDependencies.androidx_compose_bom)
+ androidTestImplementation platform(ComponentsDependencies.androidx_compose_bom)
+
+ implementation ComponentsDependencies.androidx_appcompat
+ implementation ComponentsDependencies.androidx_browser
+ implementation ComponentsDependencies.androidx_cardview
+ implementation ComponentsDependencies.androidx_compose_ui
+ implementation ComponentsDependencies.androidx_compose_ui_tooling
+ implementation ComponentsDependencies.androidx_compose_foundation
+ implementation ComponentsDependencies.androidx_compose_material
+ implementation ComponentsDependencies.androidx_compose_runtime_livedata
+ implementation ComponentsDependencies.androidx_constraintlayout
+ implementation FocusDependencies.androidx_constraint_layout_compose
+ implementation ComponentsDependencies.androidx_core_ktx
+ implementation ComponentsDependencies.androidx_fragment
+ implementation ComponentsDependencies.androidx_lifecycle_process
+ implementation ComponentsDependencies.androidx_lifecycle_viewmodel
+ implementation ComponentsDependencies.androidx_palette
+ implementation ComponentsDependencies.androidx_preferences
+ implementation ComponentsDependencies.androidx_recyclerview
+ implementation ComponentsDependencies.androidx_savedstate
+ implementation FocusDependencies.androidx_splashscreen
+ implementation FocusDependencies.androidx_transition
+ implementation ComponentsDependencies.androidx_work_runtime
+ implementation ComponentsDependencies.androidx_data_store_preferences
+
+ implementation FocusDependencies.google_play
+
+ implementation ComponentsDependencies.google_material
+
+ implementation ComponentsDependencies.thirdparty_sentry
+
+ implementation project(':browser-engine-gecko')
+ implementation project(':browser-domains')
+ implementation project(':browser-errorpages')
+ implementation project(':browser-icons')
+ implementation project(':browser-menu')
+ implementation project(':browser-state')
+ implementation project(':browser-toolbar')
+
+ implementation project(':concept-awesomebar')
+ implementation project(':concept-engine')
+ implementation project(':concept-fetch')
+ implementation project(':concept-menu')
+
+ implementation project(':compose-awesomebar')
+
+ implementation project(':feature-awesomebar')
+ implementation project(':feature-app-links')
+ implementation project(':feature-customtabs')
+ implementation project(':feature-contextmenu')
+ implementation project(':feature-downloads')
+ implementation project(':feature-findinpage')
+ implementation project(':feature-intent')
+ implementation project(':feature-prompts')
+ implementation project(':feature-session')
+ implementation project(':feature-search')
+ implementation project(':feature-tabs')
+ implementation project(':feature-toolbar')
+ implementation project(':feature-top-sites')
+ implementation project(':feature-sitepermissions')
+ implementation project(':lib-crash')
+ implementation project(':lib-crash-sentry')
+ implementation project(':lib-state')
+ implementation project(':feature-media')
+ implementation project(':lib-auth')
+ implementation project(':lib-publicsuffixlist')
+
+ implementation project(':service-glean'), {
+ exclude group: 'org.mozilla.telemetry', module: 'glean-native'
+ }
+ implementation project(':service-location')
+ implementation project(':service-nimbus')
+
+ implementation project(':support-ktx')
+ implementation project(':support-utils')
+ implementation project(':support-rusthttp')
+ implementation project(':support-rustlog')
+ implementation project(':support-license')
+
+ implementation project(':ui-autocomplete')
+ implementation project(':ui-colors')
+ implementation project(':ui-icons')
+ implementation project(':ui-tabcounter')
+ implementation project(':ui-widgets')
+ implementation project(':feature-webcompat')
+ implementation project(':feature-webcompat-reporter')
+ implementation project(':support-webextensions')
+ implementation project(':support-locale')
+ implementation project(':compose-cfr')
+
+ implementation ComponentsDependencies.kotlin_coroutines
+ debugImplementation ComponentsDependencies.leakcanary
+
+ focusImplementation FocusDependencies.adjust
+ focusImplementation FocusDependencies.install_referrer // Required by Adjust
+
+ testImplementation "org.mozilla.telemetry:glean-native-forUnitTests:${project.ext.glean_version}"
+
+ testImplementation FocusDependencies.testing_junit_api
+ testRuntimeOnly FocusDependencies.testing_junit_engine
+ testImplementation FocusDependencies.testing_junit_params
+ testImplementation ComponentsDependencies.testing_robolectric
+ testImplementation ComponentsDependencies.testing_mockito
+ testImplementation ComponentsDependencies.testing_coroutines
+ testImplementation ComponentsDependencies.androidx_work_testing
+ testImplementation ComponentsDependencies.androidx_arch_core_testing
+ testImplementation project(':support-test')
+ testImplementation project(':support-test-libstate')
+ androidTestImplementation ComponentsDependencies.androidx_espresso_core, {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ }
+ androidTestImplementation FocusDependencies.espresso_idling_resource
+ androidTestImplementation FocusDependencies.espresso_web, {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ }
+ androidTestImplementation FocusDependencies.espresso_intents
+
+
+ androidTestImplementation ComponentsDependencies.testing_mockwebserver
+ testImplementation ComponentsDependencies.testing_mockwebserver
+ testImplementation project(':lib-fetch-okhttp')
+
+ androidTestImplementation FocusDependencies.fastlane
+ androidTestImplementation FocusDependencies.falcon // Required by fastlane
+
+ androidTestImplementation FocusDependencies.espresso_contrib, {
+ exclude module: 'appcompat-v7'
+ exclude module: 'support-v4'
+ exclude module: 'support-annotations'
+ exclude module: 'recyclerview-v7'
+ exclude module: 'design'
+ exclude module: 'espresso-core'
+ }
+ testImplementation ComponentsDependencies.androidx_test_core
+ testImplementation ComponentsDependencies.androidx_test_runner
+ testImplementation ComponentsDependencies.androidx_test_rules
+
+ androidTestImplementation ComponentsDependencies.androidx_test_core
+ androidTestImplementation ComponentsDependencies.androidx_test_junit
+ androidTestImplementation ComponentsDependencies.androidx_test_uiautomator
+ androidTestImplementation ComponentsDependencies.androidx_test_runner
+ androidTestUtil FocusDependencies.androidx_orchestrator
+
+ lintChecks project(':tooling-lint')
+}
+// -------------------------------------------------------------------------------------------------
+// Dynamically set versionCode (See tools/build/versionCode.gradle
+// -------------------------------------------------------------------------------------------------
+
+android.applicationVariants.configureEach { variant ->
+ def buildType = variant.buildType.name
+
+ println("----------------------------------------------")
+ println("Variant name: " + variant.name)
+ println("Application ID: " + [variant.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
+ println("Build type: " + variant.buildType.name)
+ println("Flavor: " + variant.flavorName)
+
+ if (buildType == "release" || buildType == "nightly" || buildType == "beta") {
+ def baseVersionCode = generatedVersionCode
+ def versionName = buildType == "nightly" ?
+ "${Config.nightlyVersionName(project)}" :
+ "${Config.releaseVersionName(project)}"
+ println("versionName override: $versionName")
+
+ // The Google Play Store does not allow multiple APKs for the same app that all have the
+ // same version code. Therefore we need to have different version codes for our ARM and x86
+ // builds. See https://developer.android.com/studio/publish/versioning
+
+ // Our generated version code now has a length of 9 (See tools/gradle/versionCode.gradle).
+ // Our x86 builds need a higher version code to avoid installing ARM builds on an x86 device
+ // with ARM compatibility mode.
+
+ // AAB builds need a version code that is distinct from any APK builds. Since AAB and APK
+ // builds may run in parallel, AAB and APK version codes might be based on the same
+ // (minute granularity) time of day. To avoid conflicts, we ensure the minute portion
+ // of the version code is even for APKs and odd for AABs.
+
+ variant.outputs.each { output ->
+ def abi = output.getFilter(FilterConfiguration.FilterType.ABI.name())
+ def aab = project.hasProperty("aab")
+ // We use the same version code generator, that we inherited from Fennec, across all channels - even on
+ // channels that never shipped a Fennec build.
+
+ // ensure baseVersionCode is an even number
+ if (baseVersionCode % 2) {
+ baseVersionCode = baseVersionCode + 1
+ }
+
+ def versionCodeOverride = baseVersionCode
+
+ if (aab) {
+ // AAB version code is odd
+ versionCodeOverride = versionCodeOverride + 1
+ println("versionCode for AAB = $versionCodeOverride")
+ } else {
+ if (abi == "x86_64") {
+ versionCodeOverride = versionCodeOverride + 6
+ } else if (abi == "x86") {
+ versionCodeOverride = versionCodeOverride + 4
+ } else if (abi == "arm64-v8a") {
+ versionCodeOverride = versionCodeOverride + 2
+ } else if (abi == "armeabi-v7a") {
+ versionCodeOverride = versionCodeOverride + 0
+ } else {
+ throw new RuntimeException("Unknown ABI: " + abi)
+ }
+ println("versionCode for $abi = $versionCodeOverride")
+ }
+
+ if (versionName != null) {
+ output.versionNameOverride = versionName
+ }
+ output.versionCodeOverride = versionCodeOverride
+
+ }
+
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+// MLS: Read token from local file if it exists (Only release builds)
+// -------------------------------------------------------------------------------------------------
+
+android.applicationVariants.configureEach {
+ print("MLS token: ")
+ try {
+ def token = new File("${rootDir}/.mls_token").text.trim()
+ buildConfigField 'String', 'MLS_TOKEN', '"' + token + '"'
+ println "(Added from .mls_token file)"
+ } catch (FileNotFoundException ignored) {
+ buildConfigField 'String', 'MLS_TOKEN', '""'
+ println("X_X")
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+// Adjust: Read token from local file if it exists (Only release builds)
+// -------------------------------------------------------------------------------------------------
+
+android.applicationVariants.configureEach { variant ->
+ def variantName = variant.getName()
+
+ print("Adjust token: ")
+
+ if (variantName.contains("Release") && variantName.contains("focus")) {
+ try {
+ def token = new File("${rootDir}/.adjust_token").text.trim()
+ buildConfigField 'String', 'ADJUST_TOKEN', '"' + token + '"'
+ println "(Added from .adjust_token file)"
+ } catch (FileNotFoundException ignored) {
+ if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
+ buildConfigField 'String', 'ADJUST_TOKEN', '"fake"'
+ println("fake - only for local development")
+ } else {
+ buildConfigField 'String', 'ADJUST_TOKEN', 'null'
+ println("X_X")
+ }
+ }
+ } else {
+ buildConfigField 'String', 'ADJUST_TOKEN', 'null'
+ println("--")
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+// Sentry: Read token from local file if it exists (Only release builds)
+// -------------------------------------------------------------------------------------------------
+
+android.applicationVariants.configureEach {
+ print("Sentry token: ")
+ try {
+ def token = new File("${rootDir}/.sentry_token").text.trim()
+ buildConfigField 'String', 'SENTRY_TOKEN', '"' + token + '"'
+ println "(Added from .sentry_token file)"
+ } catch (FileNotFoundException ignored) {
+ buildConfigField 'String', 'SENTRY_TOKEN', '""'
+ println("X_X")
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+// L10N: Generate list of locales
+// Focus provides its own (Android independent) locale switcher. That switcher requires a list
+// of locale codes. We generate that list here to avoid having to manually maintain a list of locales:
+// -------------------------------------------------------------------------------------------------
+
+def getEnabledLocales() {
+ def resDir = file('src/main/res')
+
+ def potentialLanguageDirs = resDir.listFiles(new FilenameFilter() {
+ @Override
+ boolean accept(File dir, String name) {
+ return name.startsWith("values-")
+ }
+ })
+
+ def langs = potentialLanguageDirs.findAll {
+ // Only select locales where strings.xml exists
+ // Some locales might only contain e.g. sumo URLS in urls.xml, and should be skipped (see es vs es-ES/es-MX/etc)
+ return file(new File(it, "strings.xml")).exists()
+ } .collect {
+ // And reduce down to actual values-* names
+ return it.name
+ } .collect {
+ return it.substring("values-".length())
+ } .collect {
+ if (it.length() > 3 && it.contains("-r")) {
+ // Android resource dirs add an "r" prefix to the region - we need to strip that for java usage
+ // Add 1 to have the index of the r, without the dash
+ def regionPrefixPosition = it.indexOf("-r") + 1
+
+ return it.substring(0, regionPrefixPosition) + it.substring(regionPrefixPosition + 1)
+ } else {
+ return it
+ }
+ }.collect {
+ return '"' + it + '"'
+ }
+
+ // en-US is the default language (in "values") and therefore needs to be added separately
+ langs << "\"en-US\""
+
+ return langs.sort { it }
+}
+
+// -------------------------------------------------------------------------------------------------
+// Nimbus: Read endpoint from local.properties of a local file if it exists
+// -------------------------------------------------------------------------------------------------
+
+print("Nimbus endpoint: ")
+android.applicationVariants.configureEach { variant ->
+ def variantName = variant.getName()
+
+ if (!variantName.contains("Debug")) {
+ try {
+ def url = new File("${rootDir}/.nimbus").text.trim()
+ buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"'
+ println "(Added from .nimbus file)"
+ } catch (FileNotFoundException ignored) {
+ buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null'
+ println("X_X")
+ }
+ } else if (gradle.hasProperty("localProperties.nimbus.remote-settings.url")) {
+ def url = gradle.getProperty("localProperties.nimbus.remote-settings.url")
+ buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"'
+ println "(Added from local.properties file)"
+ } else {
+ buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null'
+ println("--")
+ }
+}
+
+def generatedLocaleListDir = 'src/main/java/org/mozilla/focus/generated'
+def generatedLocaleListFilename = 'LocalesList.kt'
+
+tasks.register('generateLocaleList') {
+ doLast {
+ def dir = file(generatedLocaleListDir)
+ dir.mkdir()
+ def localeList = file(new File(dir, generatedLocaleListFilename))
+
+ localeList.delete()
+ localeList.createNewFile()
+ localeList << "package org.mozilla.focus.generated" << "\n" << "\n"
+ localeList << "import java.util.Collections" << "\n"
+ localeList << "\n"
+ localeList << "/**"
+ localeList << "\n"
+ localeList << " * Provides a list of bundled locales based on the language files in the res folder."
+ localeList << "\n"
+ localeList << " */"
+ localeList << "\n"
+ localeList << "object LocalesList {" << "\n"
+ localeList << " " << "val BUNDLED_LOCALES: List = Collections.unmodifiableList("
+ localeList << "\n"
+ localeList << " " << "listOf("
+ localeList << "\n"
+ localeList << " "
+ localeList << getEnabledLocales().join(",\n" + " ")
+ localeList << ",\n"
+ localeList << " )," << "\n"
+ localeList << " )" << "\n"
+ localeList << "}" << "\n"
+ }
+}
+
+tasks.configureEach { task ->
+ if (name.contains("compile")) {
+ task.dependsOn generateLocaleList
+ }
+}
+
+clean.doLast {
+ file(generatedLocaleListDir).deleteDir()
+}
+
+if (project.hasProperty("coverage")) {
+ tasks.withType(Test).configureEach {
+ jacoco.includeNoLocationClasses = true
+ jacoco.excludes = ['jdk.internal.*']
+ }
+
+ android.applicationVariants.configureEach { variant ->
+ tasks.register("jacoco${variant.name.capitalize()}TestReport", JacocoReport) {
+
+ dependsOn(["test${variant.name.capitalize()}UnitTest"])
+ reports {
+ html.required = true
+ xml.required = true
+ }
+
+ def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
+ '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*']
+ def kotlinTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${variant.name}", excludes: fileFilter)
+ def javaTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${variant.flavorName}/${variant.buildType.name}",
+ excludes: fileFilter)
+ def mainSrc = "$project.projectDir/src/main/java"
+ sourceDirectories.setFrom(files([mainSrc]))
+ classDirectories.setFrom(files([kotlinTree, javaTree]))
+ executionData.setFrom(fileTree(dir: project.layout.buildDirectory, includes: [
+ "jacoco/test${variant.name.capitalize()}UnitTest.exec", 'outputs/code-coverage/connected/*coverage.ec'
+ ]))
+ }
+ }
+
+ android {
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ applicationIdSuffix ".coverage"
+ }
+ }
+ }
+}
+
+if (gradle.hasProperty('localProperties.autoPublish.glean.dir')) {
+ ext.gleanSrcDir = gradle."localProperties.autoPublish.glean.dir"
+ apply from: "../${gleanSrcDir}/build-scripts/substitute-local-glean.gradle"
+}
+
+// -------------------------------------------------------------------------------------------------
+// Task for printing APK information for the requested variant
+// Taskgraph Usage: "./gradlew printVariants
+// -------------------------------------------------------------------------------------------------
+tasks.register('printVariants') {
+ doLast {
+ def variants = android.applicationVariants.collect { variant -> [
+ apks: variant.outputs.collect { output -> [
+ abi: output.getFilter(FilterConfiguration.FilterType.ABI.name()),
+ fileName: output.outputFile.name
+ ]},
+ build_type: variant.buildType.name,
+ name: variant.name,
+ ]}
+ // AndroidTest is a special case not included above
+ variants.add([
+ apks: [[
+ abi: 'noarch',
+ fileName: 'app-debug-androidTest.apk',
+ ]],
+ build_type: 'androidTest',
+ name: 'androidTest',
+ ])
+ println 'variants: ' + JsonOutput.toJson(variants)
+ }
+}
+
+// Enable expiration by major version.
+ext.gleanExpireByVersion = 1
+
+afterEvaluate {
+
+ // Format test output. Copied from Fenix, which was ported from AC #2401
+ tasks.withType(Test).configureEach {
+ systemProperty "robolectric.logging", "stdout"
+ systemProperty "logging.test-mode", "true"
+
+ testLogging.events = []
+
+ def out = services.get(StyledTextOutputFactory).create("tests")
+
+ beforeSuite { descriptor ->
+ if (descriptor.getClassName() != null) {
+ out.style(Style.Header).println("\nSUITE: " + descriptor.getClassName())
+ }
+ }
+
+ beforeTest { descriptor ->
+ out.style(Style.Description).println(" TEST: " + descriptor.getName())
+ }
+
+ onOutput { descriptor, event ->
+ logger.lifecycle(" " + event.message.trim())
+ }
+
+ afterTest { descriptor, result ->
+ switch (result.getResultType()) {
+ case ResultType.SUCCESS:
+ out.style(Style.Success).println(" SUCCESS")
+ break
+
+ case ResultType.FAILURE:
+ def testId = descriptor.getClassName() + "." + descriptor.getName()
+ out.style(Style.Failure).println(" TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException())
+ break
+
+ case ResultType.SKIPPED:
+ out.style(Style.Info).println(" SKIPPED")
+ break
+ }
+ logger.lifecycle("")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/lint-baseline.xml b/mobile/android/focus-android/app/lint-baseline.xml
new file mode 100644
index 0000000000..b230739f68
--- /dev/null
+++ b/mobile/android/focus-android/app/lint-baseline.xml
@@ -0,0 +1,7196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/lint.xml b/mobile/android/focus-android/app/lint.xml
new file mode 100644
index 0000000000..f929411dd1
--- /dev/null
+++ b/mobile/android/focus-android/app/lint.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/metrics.yaml b/mobile/android/focus-android/app/metrics.yaml
new file mode 100644
index 0000000000..e0fecc694e
--- /dev/null
+++ b/mobile/android/focus-android/app/metrics.yaml
@@ -0,0 +1,2458 @@
+# 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/.
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+
+no_lint:
+ - CATEGORY_GENERIC
+ - COMMON_PREFIX
+
+browser:
+ is_default:
+ type: boolean
+ lifetime: application
+ description: |
+ Is Focus the default browser? This is true only if the user
+ changes the default browser through the app settings.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/4545
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5065#issuecomment-894328647
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ default_search_engine:
+ type: string
+ lifetime: application
+ description: |
+ A string containing the default search engine name.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/4545
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5065#issuecomment-894328647
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ send_in_pings:
+ - baseline
+ - metrics
+ no_lint:
+ - BASELINE_PING
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ locale_override:
+ type: string
+ lifetime: application
+ description: |
+ The locale that differs from the system locale if a user
+ specifically overrides it for the app.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/4545
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5065#issuecomment-894328647
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ total_uri_count:
+ type: counter
+ description: |
+ Records count of URIs visited by the user in the current session,
+ including page reloads.
+ It does not include background page requests and URIs from embedded pages
+ but may be incremented without user interaction by website scripts
+ that programmatically redirect to a new location.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5518
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5523
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - mcarare@mozilla.com
+ send_in_pings:
+ - metrics
+ - baseline
+ no_lint:
+ - BASELINE_PING
+ expires: never
+ install_source:
+ type: string
+ lifetime: application
+ description: Used to identify the source the app was installed from.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5684
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5694
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ back_button_pressed:
+ type: event
+ description: Back button has been presed on a browser tab.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5914
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5913
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ action_performed:
+ description: |
+ The action performed by pressing back button:
+ erase_to_home or erase_to_external_app.
+ type: string
+ report_site_issue_counter:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has tapped
+ the report site issue from browser menu
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5897
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5898
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+perf.startup:
+ startup_type:
+ type: labeled_counter
+ description: |
+ Indicates how the browser was started. The label is divided into two
+ variables. `state` is how cached the browser is when started. `path` is
+ what code path we are expected to take. Together, they create a combined
+ label: `state_path`. For brevity, the specific states are documented in
+ the [Fenix perf
+ glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary).
+
+ This implementation is intended to be simple, not comprehensive. We list
+ the implications below.
+
+
+ These ways of opening the app undesirably adds events to our primary
+ buckets (non-`unknown` cases):
+ - App switcher cold/warm: `cold/warm_` + duplicates path from
+ previous launch
+ - An Intent is sent internally that's uses `ACTION_MAIN` or
+ `ACTION_VIEW` could be: `*_main/view` (unknown if this ever happens)
+ - A command-line launch uses `ACTION_MAIN` or `ACTION_VIEW` could be:
+ `*_main/view`
+
+
+ These ways of opening the app undesirably do not add their events to our
+ primary buckets:
+ - Close and reopen the app very quickly: no event is recorded.
+
+
+ These ways of opening the app don't affect our primary buckets:
+ - App switcher hot: `hot_unknown`
+ - PWA (all states): `unknown_unknown`
+ - Custom tab: `unknown_view`
+ - Cold start where a service or other non-activity starts the process
+ (not manually tested) - this seems to happen if you have the homescreen
+ widget: `unknown_*`
+ - Another activity is drawn before MainActivity or CustomTabActivity
+ (e.g. widget voice
+ search): `unknown_*`
+
+
+ In addition to the events above, the `unknown` state may be chosen when we
+ were unable to determine a cause due to implementation details or the API
+ was used incorrectly. We may be able to record the events listed above
+ into different buckets but we kept the implementation simple for now.
+
+ N.B.: for implementation simplicity, we duplicate the logic in app that
+ determines `path` so it's not perfectly accurate. In one way, we record we
+ is intended to happen rather than what actually happened (e.g. the user
+ may click a link so we record VIEW but the app does a MAIN by going to the
+ homescreen because the link was invalid).
+ labels:
+ - cold_main
+ - cold_view
+ - cold_unknown
+ - warm_main
+ - warm_view
+ - warm_unknown
+ - hot_main
+ - hot_view
+ - hot_unknown
+ - unknown_main
+ - unknown_view
+ - unknown_unknown
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7079
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - perf-telemetry-alerts@mozilla.com
+ - mleclair@mozilla.com
+ expires: never
+
+activation:
+ activation_id:
+ type: uuid
+ lifetime: user
+ description: |
+ An alternate identifier, not correlated with the client_id, generated once
+ and only sent with the activation ping.
+ send_in_pings:
+ - activation
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/4545
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/issues/4901
+ data_sensitivity:
+ - highly_sensitive
+ notification_emails:
+ - android-probes@mozilla.com
+ - jalmeida@mozilla.com
+ expires: never
+
+legacy_ids:
+ client_id:
+ type: uuid
+ description: |
+ Sets the legacy client ID as part of the deletion-request ping.
+ **No longer reported set since Focus 124, where legacy telemetry was removed**.
+ send_in_pings:
+ - deletion-request
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/4545
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1805256
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5512#issuecomment-1023668181
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1805256
+ notification_emails:
+ - jalmeida@mozilla.com
+ - android-probes@mozilla.com
+ - tlong@mozilla.com
+ expires: never
+
+browser.search:
+ with_ads:
+ type: labeled_counter
+ description: |
+ Records counts of SERP pages with adverts displayed.
+ The key format is
+ `.in-content.[sap|sap-follow-on|organic].[code|none](.[channel])?`,
+ where:
+
+ * `provider-name` is the name of the provider,
+ * `sap|sap-follow-on|organic` is the search access point,
+ * `code` is set when the url matches any of the provider's code prefixes,
+ * `channel` is set to the url "channel" query parameter.
+ send_in_pings:
+ - metrics
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/4967
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1804057
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1799049
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1809447
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/4968#issuecomment-879256443
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/focus-android/pull/8109#issuecomment-1337394286
+ - https://github.com/mozilla-mobile/firefox-android/pull/523#issuecomment-1377494482
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ ad_clicks:
+ type: labeled_counter
+ description: |
+ Records clicks of adverts on SERP pages.
+ The key format is
+ `.in-content.[sap|sap-follow-on|organic].[code|none](.[channel])?`,
+ where:
+
+ * `provider-name` is the name of the provider,
+ * `sap|sap-follow-on|organic` is the search access point,
+ * `code` is set when the url matches any of the provider's code prefixes,
+ * `channel` is set to the url "channel" query parameter.
+ send_in_pings:
+ - metrics
+ - baseline
+ no_lint:
+ - BASELINE_PING
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/4967
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1804057
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1809447
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/4968#issuecomment-879256443
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/focus-android/pull/8109#issuecomment-1337394286
+ - https://github.com/mozilla-mobile/firefox-android/pull/523#issuecomment-1377494482
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ in_content:
+ type: labeled_counter
+ description: |
+ Records the type of interaction a user has on SERP pages.
+ send_in_pings:
+ - metrics
+ - baseline
+ no_lint:
+ - BASELINE_PING
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/4967
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1809447
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/4968#issuecomment-879256443
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/523#issuecomment-1377494482
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+ search_count:
+ type: labeled_counter
+ description: |
+ The labels for this counter are `.`.
+
+ If the search engine is bundled with Focus `search-engine-name` will be
+ the name of the search engine. If it's a custom search engine (defined:
+ https://github.com/mozilla-mobile/fenix/issues/1607) the value will be
+ `custom`.
+
+ `source` will be: `action`, `suggestion`
+ send_in_pings:
+ - metrics
+ - baseline
+ no_lint:
+ - BASELINE_PING
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/6229
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/6238
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - technical
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+mozilla_products:
+ has_fenix_installed:
+ type: boolean
+ lifetime: application
+ description: |
+ If Fenix is installed on the users's device.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5295
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5303
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - technical
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ is_fenix_default_browser:
+ type: boolean
+ lifetime: application
+ description: |
+ Fenix is the default browser on user's device
+ send_in_pings:
+ - metrics
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5295
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5303
+ - https://github.com/mozilla-mobile/focus-android/pull/6315
+ - https://github.com/mozilla-mobile/firefox-android/pull/632
+ data_sensitivity:
+ - technical
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+autocomplete:
+ domain_added:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has added
+ a website to the autocomplete list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5885
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5886
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ domain_removed:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has removed
+ a website from the autocomplete list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5885
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5886
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ list_order_changed:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has reordered
+ the autocomplete list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5885
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5886
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ top_sites_setting_changed:
+ type: event
+ description: |
+ Autocomplete setting for top sites has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5885
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+ favorite_sites_setting_changed:
+ type: event
+ description: |
+ Autocomplete setting for favorite sites has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5885
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+
+shortcuts:
+ shortcuts_on_home_number:
+ type: quantity
+ description: |
+ The number of shortcuts the user has on home screen,
+ 0, 1, 2, 3 or 4 (maximum)
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5056
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5189
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ unit: shortcut(s)
+ shortcut_opened_counter:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has opened
+ a website from a shortcut in the home screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5056
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5189
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ shortcut_added_counter:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has added
+ a website to shortcuts.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5056
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5189
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ shortcut_removed_counter:
+ type: labeled_counter
+ description: |
+ A counter that indicates how many times a user has removed
+ a website from shortcuts.
+ It also indicates the screen it was removed from, home or browser.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5056
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5189
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ labels:
+ - removed_from_browser_menu
+ - removed_from_home_screen
+tracking_protection:
+ toolbar_shield_clicked:
+ type: counter
+ description: |
+ A counter that indicates how many times a user has opened
+ the tracking protection settings panel from the toolbar.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+
+ tracking_protection_changed:
+ type: event
+ description: |
+ The user has changed the setting for enhanced tracking protection.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting for ETP, true for ON, false for OFF
+ type: boolean
+
+ has_ever_changed_etp:
+ type: boolean
+ description: |
+ The user has changed the setting for enhanced tracking protection
+ at least once.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/5543
+ data_sensitivity:
+ - interaction
+ lifetime: user
+ notification_emails:
+ - android-probes@mozilla.com
+ - mcarare@mozilla.com
+ expires: never
+
+ tracker_setting_changed:
+ type: event
+ description: |
+ The user has changed the advertising tracker protection state.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ extra_keys:
+ source_of_change:
+ description: The source of interaction, "Panel" or "Settings"
+ type: string
+ tracker_changed:
+ description: |
+ The tracker changed, "Advertising", "Analytics", "Social", "Content"
+ type: string
+ is_enabled:
+ description: The new setting for tracker, true for ON, false for OFF
+ type: boolean
+
+ has_social_blocked:
+ type: boolean
+ description: |
+ The user has changed the setting for enhanced tracking protection
+ at least once.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+
+ has_advertising_blocked:
+ type: boolean
+ description: |
+ The user has changed the setting for enhanced tracking protection
+ at least once.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+
+ has_analytics_blocked:
+ type: boolean
+ description: |
+ The user has changed the setting for enhanced tracking protection
+ at least once.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+
+ has_content_blocked:
+ type: boolean
+ description: |
+ The user has changed the setting for enhanced tracking protection
+ at least once.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5057
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5163
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+pro_tips:
+ tip_displayed:
+ type: event
+ description: A pro tip has been displayed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5541
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5542
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ tip_id:
+ description: |
+ The tip code of tip being displayed. Can be one of fresh_look_tip,
+ shortcuts_tip, allow_list_tip, etp_tip,request_desktop_tip.
+ Note that fresh_look_tip is automatically displayed on home screen.
+ type: string
+ link_in_tip_clicked:
+ type: event
+ description: A link in a pro tip has been clicked.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5541
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5542
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ tip_id:
+ description: |
+ The tip code of tip being clicked.
+ Can be one of fresh_look_tip, allow_list_tip, request_desktop_tip.
+ type: string
+
+preferences:
+ user_theme:
+ type: string
+ description: >
+ A string that indicates the theme.
+ Can be one of LIGHT, DARK, or FOLLOW DEVICE.
+ Default is FOLLOW DEVICE.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5519
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5526
+ - https://github.com/mozilla-mobile/focus-android/pull/7418#issuecomment-1195518264
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+
+app_opened:
+ from_icons:
+ type: event
+ description: |
+ The user has opened the app using launcher icons
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5546
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5552
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ open_type:
+ description: |
+ Can be "Launch" if Focus was not already opened or "Resume" if it was.
+ type: string
+ from_launcher_site_shortcut:
+ type: event
+ description: |
+ The user has opened the app using launcher website shortcut
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5547
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5839
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ browse_intent:
+ type: event
+ description: |
+ App was opened from a browse intent.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5547
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5839
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ text_selection_intent:
+ type: event
+ description: |
+ App was opened from a text selection intent.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5547
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5839
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ share_intent:
+ type: event
+ description: |
+ App was opened from a share intent.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5547
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5839
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_search:
+ description: Is the shared intent a search?
+ type: boolean
+
+
+add_to_home_screen:
+ dialog_displayed:
+ type: event
+ description: The dialog for adding home screen shorcut was displayed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5548
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5598
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ add_button_tapped:
+ type: event
+ description: The add(yes) option from add to home dialog was tapped.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5548
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5598
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ has_edited_title:
+ description: Did the user edit the default title provided by the app?
+ type: boolean
+ cancel_button_tapped:
+ type: event
+ description: The cancel(no) option from add to home dialog was tapped.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5548
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5598
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+set_default_browser:
+ from_app_settings:
+ type: event
+ description: |
+ The user has changed default browser from the app.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5636
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5637
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_default:
+ description: Shows if Focus was already default.
+ type: boolean
+ from_os_settings:
+ type: event
+ description: |
+ The user has opened the OS settings to set default browser.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5636
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5637
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_default:
+ description: Shows if Focus was already default.
+ type: boolean
+ learn_more_opened:
+ type: event
+ description: |
+ The user has opened the learn more link.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5636
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5637
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_default:
+ description: Shows if Focus was already default.
+ type: boolean
+
+tab_count:
+ session_button_tapped:
+ type: event
+ description: The session button has been tapped to see session list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5583
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5644
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ session_list_item_tapped:
+ type: event
+ description: The user has switched to a tab from the session list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5583
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5644
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ session_list_closed:
+ type: event
+ description: The user has closed the session list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5583
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5644
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ erase_button_tapped:
+ type: event
+ description: The erease button has been tapped to close opened sessions.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5583
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5644
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ new_tab_opened:
+ type: event
+ description: A new tab has opened.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5583
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5644
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ source:
+ description: |
+ Tab opened from "custom tab", "context menu" or from "Window.open()"
+ type: string
+ app_backgrounded:
+ type: custom_distribution
+ description: Number of opened tabs when the app has been send to background.
+ range_min: 0
+ range_max: 50
+ bucket_count: 51
+ histogram_type: linear
+ unit: tabs
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5583
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5793
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+search_bar:
+ entered_url:
+ type: event
+ description: The user has entered a full url.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5546
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5660
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ performed_search:
+ type: event
+ description: The user has entered text and performed a search.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5546
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5660
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ engine_name:
+ description: The name of the engine used to perform the search.
+ type: string
+
+show_search_suggestions:
+ enabled_from_panel:
+ type: event
+ description: The "yes" option from the suggestion panel has been tapped.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5840
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5858
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ disabled_from_panel:
+ type: event
+ description: The "no"" option from the suggestion panel has been tapped.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5840
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5858
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ changed_from_settings:
+ type: event
+ description: The enabled state has been changed from the settings screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5840
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5858
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting value, true for ON, false for OFF
+ type: boolean
+
+downloads:
+ download_started:
+ type: event
+ description: A download has been started.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5650
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5663
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ download_paused:
+ type: event
+ description: A download has been paused.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5650
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5663
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ download_canceled:
+ type: event
+ description: A download has been cancelled.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5650
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5663
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ download_completed:
+ type: event
+ description: A download has been completed successfully.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5650
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5663
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ download_failed:
+ type: event
+ description: The download has failed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5650
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5663
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ file_extension:
+ description: The extension of the downloaded file.
+ type: string
+ open_button_tapped:
+ type: event
+ description: The open button from download confirmation was tapped.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5650
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5663
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ open_successful:
+ description: Did the user succeed in opening the downloaded file?
+ type: boolean
+ file_extension:
+ description: The extension of the downloaded file.
+ type: string
+
+search_engines:
+ open_settings:
+ type: event
+ description: The user has opened the search engines settings page.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ add_engine_tapped:
+ type: event
+ description: The user has tapped on the add another search engine button.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ save_engine_tapped:
+ type: event
+ description: The user has tried to save a custom engine.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ save_successful:
+ description: If the engine has been saved successfully.
+ type: boolean
+ set_default:
+ type: event
+ description: The user has set a search engine as default.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ engine_type:
+ description: |
+ The engine type set as default. Can be either "custom" or "bundled".
+ type: string
+ remove_engines:
+ type: event
+ description: The user has removed search engines.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ engine_count:
+ description: How many search engines has the user removed
+ type: quantity
+ open_remove_screen:
+ type: event
+ description: The user has clicked the remove option from menu.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ current_engines_count:
+ description: How many search engines did the user had at that point.
+ type: quantity
+ restore_default_engines:
+ type: event
+ description: The user has restored the default search engines
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ current_engines_count:
+ description: How many search engines did the user had at that point.
+ type: quantity
+ learn_more_tapped:
+ type: event
+ description: The learn more button was tapped.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5646
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5713
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+open_with:
+ list_displayed:
+ type: event
+ description: The list of apps has been opened.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5654
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5703
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ list_size:
+ description: The number of apps in the list
+ type: quantity
+ list_item_tapped:
+ type: event
+ description: The uer has opened the url with a app from the list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5654
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5703
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ package_name:
+ description: The app the user has chosen is a Mozilla product.
+ type: boolean
+ install_firefox:
+ type: event
+ description: The user has clicked install Firefox from store item.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5654
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5703
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+crash_reporter:
+ displayed:
+ type: event
+ description: The crash report has been displayed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5652
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5725
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ close_report:
+ type: event
+ description: The crash report has been submitted.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5652
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5725
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ submit_report:
+ description: Did the user choose to send the report?
+ type: boolean
+
+browser_menu:
+ navigation_toolbar_action:
+ type: event
+ description: The user has tapped on a navigation toolbar item.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5648
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5748
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ item:
+ description: |
+ A string containing the name of the item the user tapped:
+ back, forward, share, reload, stop
+ type: string
+ browser_menu_action:
+ type: event
+ description: The user has tapped on a browser menu item.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5648
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5748
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ item:
+ description: |
+ A string containing the name of the item the user tapped:
+ add_to_homescreen, desktop_view_off, desktop_view_on,
+ find_in_page, open_in_app, settings
+ type: string
+
+custom_tabs_toolbar:
+ navigation_toolbar_action:
+ type: event
+ description: The user tapped on a navigation toolbar item in a custom tab.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5649
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5748
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ item:
+ description: |
+ A string containing the name of the item the user tapped:
+ back, forward, reload, stop
+ type: string
+ browser_menu_action:
+ type: event
+ description: The user tapped on a browser menu item item in a custom tab.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5649
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5748
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ item:
+ description: |
+ A string containing the name of the item the user tapped:
+ desktop_view_off, desktop_view_on, find_in_page, open_in_app,
+ add_to_homescreen, open_in browser
+ type: string
+ close_tab_tapped:
+ type: event
+ description: The user has closed a custom tab.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5649
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5748
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ action_button_tapped:
+ type: event
+ description: The user has tapped the actionbutton a custom tab.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5649
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5748
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+tracking_protection_exceptions:
+ allow_list_opened:
+ type: event
+ description: The user has opened the exceptions list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5753
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5758
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ allow_list_cleared:
+ type: event
+ description: The user has removed all items from exceptions list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5753
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5758
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ list_size:
+ description: The number of exceptions in the list.
+ type: quantity
+ selected_items_removed:
+ type: event
+ description: The user has removed the selected items from exceptions list.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5753
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5758
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ list_size:
+ description: The number of selected items removed.
+ type: quantity
+
+notifications:
+ open_button_tapped:
+ type: event
+ description: The user has tapped the Open option button from notification.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5651
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5769
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ erase_open_button_tapped:
+ type: event
+ description: The user has tapped the Erase & Open button from notification.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5651
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5769
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ notification_tapped:
+ type: event
+ description: The user has tapped the notification to close the app.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5651
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5769
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ permission_granted:
+ type: boolean
+ description: |
+ True if notifications are allowed from OS settings, otherwise false.
+ Prior to Android 13, notifications were allowed by default;
+ starting with Android 13,the user must explicitly grant the permission.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1803358
+ data_reviews:
+ - https://github.com/mozilla-mobile/firefox-android/pull/475
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+
+app_shortcuts:
+ just_erase_button_tapped:
+ type: event
+ description: The user has tapped the Erase option button from shortcuts.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5651
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5769
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+ erase_open_button_tapped:
+ type: event
+ description: The user has tapped the Erase & Open button from shortcuts.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5651
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5769
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ opened_tabs:
+ description: Number of currently opened tabs
+ type: quantity
+
+recent_apps:
+ app_removed_from_list:
+ type: event
+ description: The user removed the apps from recent apps screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5651
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5769
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+context_menu:
+ item_tapped:
+ type: event
+ description: The user has tapped an option from context menu.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5647
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5773
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ item_name:
+ description: |
+ The name of the item that was tapped. One of the following:
+ open_in_new_tab, open_in_private_tab, open_image_in_new_tab,
+ save_image, share_link, copy_link, copy_image_location, share_image
+ type: string
+
+onboarding:
+ first_screen_close_button:
+ type: event
+ description: The user has tapped on close button from the first screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7500
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7566#issuecomment-1235551604
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ get_started_button:
+ type: event
+ description: The user has tapped on get started button.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7500
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7566#issuecomment-1235551604
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ default_browser_button:
+ type: event
+ description: The user has tapped on set as default browser button.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7500
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7566#issuecomment-1235551604
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ skip_button:
+ type: event
+ description: The user has tapped on skip button.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7500
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7566#issuecomment-1235551604
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ second_screen_close_button:
+ type: event
+ description: The user has tapped on close button from the second screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7500
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7566#issuecomment-1235551604
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ page_displayed:
+ type: event
+ description: A page from onboarding has been displayed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5635
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5869
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ current_item:
+ description: The curent displayed item position.
+ type: quantity
+ skip_button_tapped:
+ type: event
+ description: The user has tapped to skip onboarding.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5635
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5869
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ current_item:
+ description: The curent displayed item position.
+ type: quantity
+ finish_button_tapped:
+ type: event
+ description: The user has tapped to finish onboarding.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5635
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5869
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ current_item:
+ description: The curent displayed item position.
+ type: quantity
+ next_button_tapped:
+ type: event
+ description: The user has tapped next onboarding.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5635
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5869
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ current_item:
+ description: The curent displayed item position.
+ type: quantity
+
+search_suggestions:
+ suggestion_tapped:
+ type: event
+ description: Search suggestion selected.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5662
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5864
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ engine_name:
+ description: The name of the engine used to perform the search.
+ type: string
+ search_tapped:
+ type: event
+ description: The typed text search was selected.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5662
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5864
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ engine_name:
+ description: The name of the engine used to perform the search.
+ type: string
+ autocomplete_arrow_tapped:
+ type: event
+ description: Search suggestion selected.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5662
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5864
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+
+advanced_settings:
+ remote_debug_setting_changed:
+ type: event
+ description: |
+ Remote debugging setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5653
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+ open_links_setting_changed:
+ type: event
+ description: |
+ Open links setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5653
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+privacy_settings:
+ telemetry_setting_changed:
+ type: event
+ description: |
+ Telemetry setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5653
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+ safe_browsing_setting_changed:
+ type: event
+ description: |
+ Safe browsing setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5653
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+ unlock_setting_changed:
+ type: event
+ description: |
+ Biometric unlock setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5653
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+ stealth_setting_changed:
+ type: event
+ description: |
+ Stealth setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/5653
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/5940
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: The new setting true for ON, false for OFF
+ type: boolean
+ block_cookies_changed:
+ type: event
+ description: |
+ Block cookies setting has changed.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/6097
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/6105
+ - https://github.com/mozilla-mobile/focus-android/pull/7906
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ - rtestard@mozilla.com
+ expires: never
+ extra_keys:
+ is_enabled:
+ description: |
+ A string containing the new block cookies option the user has chosen:
+ yes, third_party_only, third_party_tracker, cross_site, no
+ type: string
+
+metrics:
+ start_reason_process_error:
+ type: boolean
+ description: |
+ The `AppStartReasonProvider.ProcessLifecycleObserver.onCreate` was
+ unexpectedly called twice. We can use this metric to validate our
+ assumptions about how these APIs are called. This probe can be removed
+ once we validate these assumptions.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7079
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-telemetry-alerts@mozilla.com
+ - mleclair@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Performance
+ start_reason_activity_error:
+ type: boolean
+ description: |
+ The `AppStartReasonProvider.ActivityLifecycleCallbacks.onActivityCreated`
+ was unexpectedly called twice. We can use this metric to validate our
+ assumptions about how these APIs are called. This probe can be removed
+ once we validate these assumptions.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7079
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - perf-telemetry-alerts@mozilla.com
+ - mleclair@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Performance
+ search_widget_installed:
+ type: boolean
+ lifetime: application
+ description: |
+ Whether or not the search widget is installed
+ send_in_pings:
+ - metrics
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7474
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Search
+
+search_widget:
+ new_tab_button:
+ type: event
+ description: |
+ A user pressed anywhere from the Focus logo until the start of the
+ microphone icon, opening a new tab search screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7474
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Search
+ voice_button:
+ type: event
+ description: |
+ A user pressed the microphone icon, opening a new voice search screen.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7474
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Search
+ promote_dialog_shown:
+ type: event
+ description: |
+ Promote search widget dialog is shown to the user.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7506
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7657#issuecomment-1252242947
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ - https://github.com/mozilla-mobile/firefox-android/pull/4040
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Search
+ add_to_home_screen_button:
+ type: event
+ description: |
+ The user has pressed on add search widget
+ to home screen button from promote dialog.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7506
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7657#issuecomment-1252242947
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Search
+ widget_was_added:
+ type: event
+ description: |
+ The user has added successfully the search widget from
+ promote search widget dialog.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7506
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/7657#issuecomment-1252242947
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Search
+cookie_banner:
+ visited_setting:
+ type: event
+ description: A user visited the cookie banner handling screen
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7965
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/8008#issuecomment-1322266028
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ setting_changed:
+ type: event
+ description: |
+ A user changed their setting.
+ extra_keys:
+ cookie_banner_setting:
+ description: |
+ The new setting for cookie banner handling: disabled,reject_all,
+ or reject_or_accept_all.
+ type: string
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/7965
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/8008#issuecomment-1322266028
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ cookie_banner_cfr_shown:
+ type: event
+ description: |
+ Cfr for cookie banner is shown to the user.
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ exception_added:
+ type: event
+ description: |
+ A user added a cookie banner handling exception through
+ the toggle in the protections panel.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797578
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/8124#issuecomment-1344449866
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ report_site_domain:
+ type: url
+ description: |
+ A user can report a site domain(Ex. for https://edition.cnn.com/
+ site domain will be cnn.com) when the cookie banner reducer is not
+ working from the cookie banner details panel.
+ lifetime: ping
+ send_in_pings:
+ - cookie-banner-report-site
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1803589
+ data_reviews:
+ - https://github.com/mozilla-mobile/firefox-android/pull/389#pullrequestreview-1341440145
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - technical
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ report_site_cancel_button:
+ type: event
+ description: |
+ The user has pressed the report site domain cancel button
+ from the cookie banner reducer details panel.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1803589
+ data_reviews:
+ - https://github.com/mozilla-mobile/firefox-android/pull/389#pullrequestreview-1341440145
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ report_domain_site_button:
+ type: event
+ description: |
+ The user has pressed the report site domain button
+ from the cookie banner reducer details panel.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1803589
+ data_reviews:
+ - https://github.com/mozilla-mobile/firefox-android/pull/389#pullrequestreview-1341440145
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ exception_removed:
+ type: event
+ description: |
+ A user removed a cookie banner handling
+ exception through the toggle in the protections panel.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797578
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/8124#issuecomment-1344449866
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+
+ visited_panel:
+ type: event
+ description: A user visited the cookie banner exception panel
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1797578
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/pull/8124#issuecomment-1344449866
+ - https://github.com/mozilla-mobile/firefox-android/pull/3320
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
diff --git a/mobile/android/focus-android/app/nimbus.fml.yaml b/mobile/android/focus-android/app/nimbus.fml.yaml
new file mode 100644
index 0000000000..fb9ba783a7
--- /dev/null
+++ b/mobile/android/focus-android/app/nimbus.fml.yaml
@@ -0,0 +1,48 @@
+about:
+ description: Nimbus Feature Manifest for Focus Android
+ kotlin:
+ package: org.mozilla.focus
+ class: .nimbus.FocusNimbus
+channels:
+ - debug
+ - nightly
+ - beta
+ - release
+features:
+ onboarding:
+ description: Nimbus feature name intended to control the onboarding plus all CFRs in the app.
+ variables:
+ is-enabled:
+ description: If `true`, the app will show the new onboarding screen
+ type: Boolean
+ default: true
+ is-cfr-enabled:
+ description: If `true`, the app will show the cfrs
+ type: Boolean
+ default: false
+ is-promote-search-widget-dialog-enabled:
+ description: If `true`, the app will show the new dialog for promote search widget
+ type: Boolean
+ default: false
+ defaults:
+ - channel: debug
+ value: {
+ "is-enabled": true,
+ "is-cfr-enabled": true,
+ "is-promote-search-widget-dialog-enabled": true,
+ }
+ cookie-banner:
+ description: Nimbus feature name intended to control the cookie banner handling in the app.
+ variables:
+ is-cookie-handling-enabled:
+ description: If 'true' , the app will show the settings part for cookie banner handling
+ type: Boolean
+ default: false
+ defaults:
+ - channel: debug
+ value: {
+ "is-cookie-handling-enabled": true
+ }
+types:
+ objects: { }
+ enums: { }
diff --git a/mobile/android/focus-android/app/pings.yaml b/mobile/android/focus-android/app/pings.yaml
new file mode 100644
index 0000000000..4816596f7c
--- /dev/null
+++ b/mobile/android/focus-android/app/pings.yaml
@@ -0,0 +1,31 @@
+# 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/.
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+activation:
+ description: |
+ This ping is intended to provide a measure of the activation of mobile
+ products. It's generated when Focus starts, right after Glean is
+ initialized.
+ include_client_id: false
+ bugs:
+ - https://github.com/mozilla-mobile/focus-android/issues/4545
+ data_reviews:
+ - https://github.com/mozilla-mobile/focus-android/issues/4901
+ notification_emails:
+ - jalmeida@mozilla.com
+
+cookie-banner-report-site:
+ description: |
+ This ping is needed when the cookie banner reducer doesn't work on
+ a website, and the user wants to report the site.
+ This ping doesn't include a client id.
+ include_client_id: false
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1803589
+ data_reviews:
+ - https://github.com/mozilla-mobile/firefox-android/pull/389#pullrequestreview-1341440145
+ notification_emails:
+ - android-probes@mozilla.com
diff --git a/mobile/android/focus-android/app/proguard-rules.pro b/mobile/android/focus-android/app/proguard-rules.pro
new file mode 100644
index 0000000000..fac523684b
--- /dev/null
+++ b/mobile/android/focus-android/app/proguard-rules.pro
@@ -0,0 +1,154 @@
+
+# We do not want to obfuscate - It's just painful to debug without the right mapping file.
+# If we update this, we'll have to update our Sentry config to upload ProGuard mappings.
+-dontobfuscate
+
+
+##### Default proguard settings:
+
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/sebastian/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+####################################################################################################
+# Adjust
+####################################################################################################
+
+-keep public class com.adjust.sdk.** { *; }
+-keep class com.google.android.gms.common.ConnectionResult {
+ int SUCCESS;
+}
+-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
+ com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
+}
+-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
+ java.lang.String getId();
+ boolean isLimitAdTrackingEnabled();
+}
+-keep class dalvik.system.VMRuntime {
+ java.lang.String getRuntime();
+}
+-keep class android.os.Build {
+ java.lang.String[] SUPPORTED_ABIS;
+ java.lang.String CPU_ABI;
+}
+-keep class android.content.res.Configuration {
+ android.os.LocaledList getLocales();
+ java.util.Locale locale;
+}
+-keep class android.os.LocaledList {
+ java.util.Locale get(int);
+}
+
+
+####################################################################################################
+# Okhttp
+####################################################################################################
+
+# JSR 305 annotations are for embedding nullability information.
+-dontwarn javax.annotation.**
+
+# A resource is loaded with a relative path so the package of this class must be preserved.
+-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
+
+# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
+-dontwarn org.codehaus.mojo.animal_sniffer.*
+
+# OkHttp platform used only on JVM and when Conscrypt dependency is available.
+-dontwarn okhttp3.internal.platform.ConscryptPlatform
+
+####################################################################################################
+# Sentry
+####################################################################################################
+
+# Recommended config via https://docs.sentry.io/clients/java/modules/android/#manual-integration
+# Since we don't obfuscate, we don't need to use their Gradle plugin to upload ProGuard mappings.
+-keepattributes LineNumberTable,SourceFile
+-dontwarn org.slf4j.**
+-dontwarn javax.**
+
+# Our addition: this class is saved to disk via Serializable, which ProGuard doesn't like.
+# If we exclude this, upload silently fails (Sentry swallows a NPE so we don't crash).
+# I filed https://github.com/getsentry/sentry-java/issues/572
+#
+# If Sentry ever mysteriously stops working after we upgrade it, this could be why.
+-keep class io.sentry.event.Event { *; }
+
+####################################################################################################
+# Android architecture components
+####################################################################################################
+
+-dontwarn android.**
+-dontwarn androidx.**
+-dontwarn com.google.**
+-dontwarn org.mozilla.geckoview.**
+-dontwarn mozilla.components.**
+
+# https://developer.android.com/topic/libraries/architecture/release-notes.html
+# According to the docs this won't be needed when 1.0 of the library is released.
+-keep class * implements android.arch.lifecycle.GeneratedAdapter {(...);}
+
+# Temporary fix until we can use androidx
+-dontwarn mozilla.components.service.fretboard.scheduler.workmanager.**
+
+# Fix for ViewModels
+-keep class * extends androidx.lifecycle.ViewModel {
+ ();
+}
+-keep class * extends androidx.lifecycle.AndroidViewModel {
+ (android.app.Application);
+}
+
+####################################################################################################
+# Mozilla Application Services
+####################################################################################################
+
+-keep class mozilla.appservices.** { *; }
+
+####################################################################################################
+# Kotlinx
+####################################################################################################
+
+-dontwarn kotlinx.atomicfu.**
+
+####################################################################################################
+# snakeyaml
+####################################################################################################
+
+-dontwarn java.beans.PropertyDescriptor
+-dontwarn java.beans.Introspector
+-dontwarn java.beans.BeanInfo
+-dontwarn java.beans.IntrospectionException
+-dontwarn java.beans.FeatureDescriptor
+
+####################################################################################################
+# REMOVE all Log messages except warnings and errors
+####################################################################################################
+-assumenosideeffects class android.util.Log {
+ public static boolean isLoggable(java.lang.String, int);
+ public static int v(...);
+ public static int i(...);
+ public static int d(...);
+}
+
+####################################################################################################
+# kotlinx.coroutines: use the fast service loader to init MainDispatcherLoader by including a rule
+# to rewrite this property to return true:
+# https://github.com/Kotlin/kotlinx.coroutines/blob/8c98180f177bbe4b26f1ed9685a9280fea648b9c/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt#L19
+#
+# R8 is expected to optimize the default implementation to avoid a performance issue but a bug in R8
+# as bundled with AGP v7.0.0 causes this optimization to fail so we use the fast service loader instead. See:
+# https://github.com/mozilla-mobile/focus-android/issues/5102#issuecomment-897854121
+#
+# The fast service loader appears to be as performant as the R8 optimization so it's not worth the
+# churn to later remove this workaround. If needed, the upstream fix is being handled in
+# https://issuetracker.google.com/issues/196302685
+####################################################################################################
+-assumenosideeffects class kotlinx.coroutines.internal.MainDispatcherLoader {
+ boolean FAST_SERVICE_LOADER_ENABLED return true;
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/audioPage.html b/mobile/android/focus-android/app/src/androidTest/assets/audioPage.html
new file mode 100644
index 0000000000..f45ba6410f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/audioPage.html
@@ -0,0 +1,37 @@
+
+
+ Audio_Test_Page
+
+
+Page content: audio player
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/cross-site-cookies.html b/mobile/android/focus-android/app/src/androidTest/assets/cross-site-cookies.html
new file mode 100644
index 0000000000..5cf99f3881
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/cross-site-cookies.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+known-tracker.englehardt-tracker.com
+different site, cross-origin iframe, on blocklist
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/download.jpg b/mobile/android/focus-android/app/src/androidTest/assets/download.jpg
new file mode 100644
index 0000000000..bb55dd7063
Binary files /dev/null and b/mobile/android/focus-android/app/src/androidTest/assets/download.jpg differ
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/etpPages/adsTrackers.html b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/adsTrackers.html
new file mode 100644
index 0000000000..b8e0f7bc55
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/adsTrackers.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ adsTrackers
+
+
+
+
+ads trackers:
+if you can read this, then:
+ads trackers not blocked
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/etpPages/analyticsTrackers.html b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/analyticsTrackers.html
new file mode 100644
index 0000000000..e97a7e35c6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/analyticsTrackers.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ analyticsTrackers
+
+
+
+
+analytics trackers
+if you can read this, then:
+analytics trackers not blocked
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/etpPages/otherTrackers.html b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/otherTrackers.html
new file mode 100644
index 0000000000..5e4bd63a78
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/otherTrackers.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ otherTrackers
+
+
+
+
+Level 2 (Strict List) Tracker Blocking
+other content trackers
+if you can read this, then:
+other content trackers not blocked
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/etpPages/socialTrackers.html b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/socialTrackers.html
new file mode 100644
index 0000000000..5f1afd19aa
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/etpPages/socialTrackers.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ socialTrackers
+
+
+
+
+social trackers
+if you can read this, then:
+social trackers not blocked
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/genericPage.html b/mobile/android/focus-android/app/src/androidTest/assets/genericPage.html
new file mode 100644
index 0000000000..46f36bf6d1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/genericPage.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ GenericPage
+
+
+
+
+groovy rabbits
+This test page does nothing.
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/global_privacy_control.html b/mobile/android/focus-android/app/src/androidTest/assets/global_privacy_control.html
new file mode 100644
index 0000000000..e08df8c17f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/global_privacy_control.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/htmlControls.html b/mobile/android/focus-android/app/src/androidTest/assets/htmlControls.html
new file mode 100644
index 0000000000..3677417a28
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/htmlControls.html
@@ -0,0 +1,66 @@
+
+
+
+ Html_Control_Form
+
+
+
+
+Misc Link Types
+
+
+
+
+Drop-down Form
+
+ The Only Ones
+ The National
+
+ Submit drop down option
+
+
+
+
+Calendar Form
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/image_test.html b/mobile/android/focus-android/app/src/androidTest/assets/image_test.html
new file mode 100644
index 0000000000..ea8ea10f53
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/image_test.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ gigantic experience
+
+
+
+
+
+groovy rabbits
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/mutedVideoPage.html b/mobile/android/focus-android/app/src/androidTest/assets/mutedVideoPage.html
new file mode 100644
index 0000000000..8c4fbfc686
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/mutedVideoPage.html
@@ -0,0 +1,53 @@
+
+
+ Muted_Video_Test_Page
+
+
+Page content: muted video player
+
+
+
+ Play
+ Pause
+ Full Screen
+
+
+
+ Your browser does not support HTML video.
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/rabbit.jpg b/mobile/android/focus-android/app/src/androidTest/assets/rabbit.jpg
new file mode 100644
index 0000000000..3225407b1c
Binary files /dev/null and b/mobile/android/focus-android/app/src/androidTest/assets/rabbit.jpg differ
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/resources/audioSample.mp3 b/mobile/android/focus-android/app/src/androidTest/assets/resources/audioSample.mp3
new file mode 100644
index 0000000000..eb0420a48b
Binary files /dev/null and b/mobile/android/focus-android/app/src/androidTest/assets/resources/audioSample.mp3 differ
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/resources/clip.mp4 b/mobile/android/focus-android/app/src/androidTest/assets/resources/clip.mp4
new file mode 100644
index 0000000000..20f739c7c8
Binary files /dev/null and b/mobile/android/focus-android/app/src/androidTest/assets/resources/clip.mp4 differ
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/same-site-cookies.html b/mobile/android/focus-android/app/src/androidTest/assets/same-site-cookies.html
new file mode 100644
index 0000000000..dd4fa31be7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/same-site-cookies.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+Rerun Tests
+
+cookies
+
+
+localStorage
+
+
+
+
+
+
+Storage Access API
+requestStorageAccess()
+Return value of requestStorageAccess():
not yet called
+hasStorageAccess()
+Return value of hasStorageAccess():
not yet called
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/service-worker.js b/mobile/android/focus-android/app/src/androidTest/assets/service-worker.js
new file mode 100644
index 0000000000..8f77e519df
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/service-worker.js
@@ -0,0 +1,2 @@
+// Just some token we are looking for on disk
+const KANGAROO = true;
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/storage_check.html b/mobile/android/focus-android/app/src/androidTest/assets/storage_check.html
new file mode 100644
index 0000000000..c52cae9b7b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/storage_check.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+Storage check
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/storage_start.html b/mobile/android/focus-android/app/src/androidTest/assets/storage_start.html
new file mode 100644
index 0000000000..e88f7f06d1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/storage_start.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+Storage Start
+
+
+Set cookies
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/tab1.html b/mobile/android/focus-android/app/src/androidTest/assets/tab1.html
new file mode 100644
index 0000000000..4a9c8ce88e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/tab1.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ tab1
+
+
+ Tab 1
+
+ Tab 2
+
+ Tab 3
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/tab2.html b/mobile/android/focus-android/app/src/androidTest/assets/tab2.html
new file mode 100644
index 0000000000..be5f65e6a5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/tab2.html
@@ -0,0 +1,16 @@
+
+
+
+
+ tab2
+
+
+
+ Tab 2
+
+ Tab 1
+
+ Tab 3
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/tab3.html b/mobile/android/focus-android/app/src/androidTest/assets/tab3.html
new file mode 100644
index 0000000000..fc8c08446c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/tab3.html
@@ -0,0 +1,20 @@
+
+
+
+
+ tab3
+
+
+
+ Tab 3
+
+ Tab 1
+
+ Tab 2
+
+
+ Mozilla Youtube link
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/test.html b/mobile/android/focus-android/app/src/androidTest/assets/test.html
new file mode 100644
index 0000000000..7273622e6f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/test.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+ gigantic experience
+
+
+focus test page
+
+groovy rabbits
+This test page installs a service worker and saves a cookie.
+
+Cookie
+Initial:
+
+
+
+
+Afterwards:
+
+Service worker
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/assets/videoPage.html b/mobile/android/focus-android/app/src/androidTest/assets/videoPage.html
new file mode 100644
index 0000000000..cd352268b3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/assets/videoPage.html
@@ -0,0 +1,53 @@
+
+
+ Video_Test_Page
+
+
+Page content: video player
+
+
+
+ Play
+ Pause
+ Full Screen
+
+
+
+ Your browser does not support HTML video.
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/AddToHomescreenTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/AddToHomescreenTest.kt
new file mode 100644
index 0000000000..3fae3efa46
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/AddToHomescreenTest.kt
@@ -0,0 +1,89 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericTabAsset
+import org.mozilla.focus.helpers.TestHelper.randomString
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+/**
+ * Tests to verify the functionality of Add to homescreen from the main menu
+ */
+@RunWith(AndroidJUnit4ClassRunner::class)
+class AddToHomescreenTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun setup() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun addPageToHomeScreenTest() {
+ val pageUrl = getGenericTabAsset(webServer, 1).url
+ val pageTitle = randomString(5)
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ }.openMainMenu {
+ }.openAddToHSDialog {
+ addShortcutWithTitle(pageTitle)
+ handleAddAutomaticallyDialog()
+ }.searchAndOpenHomeScreenShortcut(pageTitle) {
+ verifyPageURL(pageUrl)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun noNameShortcutTest() {
+ val pageUrl = getGenericTabAsset(webServer, 1).url
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ }.openMainMenu {
+ }.openAddToHSDialog {
+ // leave shortcut title empty and add it to HS
+ addShortcutNoTitle()
+ handleAddAutomaticallyDialog()
+ }.searchAndOpenHomeScreenShortcut(webServer.hostName) {
+ // only checking a part of the URL that is constant,
+ // in case it opens a different shortcut on a retry
+ verifyPageURL("tab1.html")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ContextMenusTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ContextMenusTest.kt
new file mode 100644
index 0000000000..8f74566efe
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ContextMenusTest.kt
@@ -0,0 +1,187 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.DeleteFilesHelper.deleteFileUsingDisplayName
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityIntentsTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.StringsHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericTabAsset
+import org.mozilla.focus.helpers.TestAssetHelper.getImageTestAsset
+import org.mozilla.focus.helpers.TestHelper
+import org.mozilla.focus.helpers.TestHelper.assertNativeAppOpens
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.helpers.TestHelper.permAllowBtn
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// These tests check the interaction with various context menu options
+@RunWith(AndroidJUnit4ClassRunner::class)
+class ContextMenusTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityIntentsTestRule(showFirstRun = false)
+
+ @get: Rule
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun setup() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun linkedImageContextMenuItemsTest() {
+ val imagesTestPage = getImageTestAsset(webServer)
+ val imageAssetUrl = webServer.url("download.jpg").toString()
+
+ searchScreen {
+ }.loadPage(imagesTestPage.url) {
+ longPressLink("download icon")
+ verifyImageContextMenu(true, imageAssetUrl)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun simpleImageContextMenuItemsTest() {
+ val imagesTestPage = getImageTestAsset(webServer)
+ val imageAssetUrl = webServer.url("rabbit.jpg").toString()
+
+ searchScreen {
+ }.loadPage(imagesTestPage.url) {
+ longPressLink("rabbit.jpg")
+ verifyImageContextMenu(false, imageAssetUrl)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun linkContextMenuItemsTest() {
+ val tab1Page = getGenericTabAsset(webServer, 1)
+ val tab2Page = getGenericTabAsset(webServer, 2)
+
+ searchScreen {
+ }.loadPage(tab1Page.url) {
+ verifyPageContent("Tab 1")
+ longPressLink("Tab 2")
+ verifyLinkContextMenu(tab2Page.url)
+ }
+ }
+
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1819872")
+ @SmokeTest
+ @Test
+ fun copyLinkAddressTest() {
+ val tab1Page = getGenericTabAsset(webServer, 1)
+ val tab2Page = getGenericTabAsset(webServer, 2)
+
+ searchScreen {
+ }.loadPage(tab1Page.url) {
+ longPressLink("Tab 2")
+ verifyLinkContextMenu(tab2Page.url)
+ clickContextMenuCopyLink()
+ }.openSearchBar {
+ clearSearchBar()
+ longPressSearchBar()
+ }.pasteAndLoadLink {
+ progressBar.waitUntilGone(TestHelper.waitingTime)
+ verifyPageURL(tab2Page.url)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun shareLinkTest() {
+ val tab1Page = getGenericTabAsset(webServer, 1)
+ val tab2Page = getGenericTabAsset(webServer, 2)
+
+ searchScreen {
+ }.loadPage(tab1Page.url) {
+ longPressLink("Tab 2")
+ verifyLinkContextMenu(tab2Page.url)
+ clickShareLink()
+ verifyShareAppsListOpened()
+ }
+ }
+
+ @Test
+ fun copyImageLocationTest() {
+ val imagesTestPage = getImageTestAsset(webServer)
+ val imageAssetUrl = webServer.url("rabbit.jpg").toString()
+
+ searchScreen {
+ }.loadPage(imagesTestPage.url) {
+ longPressLink("rabbit.jpg")
+ verifyImageContextMenu(false, imageAssetUrl)
+ clickCopyImageLocation()
+ }.openSearchBar {
+ clearSearchBar()
+ longPressSearchBar()
+ }.pasteAndLoadLink {
+ progressBar.waitUntilGone(TestHelper.waitingTime)
+ verifyPageURL(imageAssetUrl)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun saveImageTest() {
+ val imagesTestPage = getImageTestAsset(webServer)
+ val fileName = "rabbit.jpg"
+
+ searchScreen {
+ }.loadPage(imagesTestPage.url) {
+ longPressLink(fileName)
+ }.clickSaveImage {
+ // If permission dialog appears on devices with API<30, grant it
+ if (permAllowBtn.exists()) {
+ permAllowBtn.click()
+ }
+ verifyDownloadConfirmationMessage(fileName)
+ openDownloadedFile()
+ assertNativeAppOpens(StringsHelper.GOOGLE_PHOTOS)
+ }
+ deleteFileUsingDisplayName(
+ getTargetContext.applicationContext,
+ fileName,
+ )
+ }
+
+ @Test
+ fun shareImageTest() {
+ val imagesTestPage = getImageTestAsset(webServer)
+
+ searchScreen {
+ }.loadPage(imagesTestPage.url) {
+ longPressLink("rabbit.jpg")
+ clickShareImage()
+ verifyShareAppsListOpened()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/CustomTabTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/CustomTabTest.kt
new file mode 100644
index 0000000000..35ff64cd37
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/CustomTabTest.kt
@@ -0,0 +1,133 @@
+/* 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/. */
+
+@file:Suppress("DEPRECATION")
+
+package org.mozilla.focus.activity
+
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.launchActivity
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import androidx.test.rule.ActivityTestRule
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.customTab
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericAsset
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericTabAsset
+import org.mozilla.focus.helpers.TestHelper.createCustomTabIntent
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+import java.io.IOException
+
+@RunWith(AndroidJUnit4ClassRunner::class)
+class CustomTabTest {
+ private lateinit var webServer: MockWebServer
+ private val MENU_ITEM_LABEL = "TestItem4223"
+ private val ACTION_BUTTON_DESCRIPTION = "TestButton"
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val activityTestRule = ActivityTestRule(
+ IntentReceiverActivity::class.java,
+ true,
+ false,
+ )
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setShowStartBrowsingCfrEnabled(false)
+ featureSettingsHelper.setCookieBannerReductionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun testCustomTabUI() {
+ val customTabPage = getGenericAsset(webServer)
+ val customTabActivity =
+ launchActivity(
+ createCustomTabIntent(customTabPage.url, MENU_ITEM_LABEL, ACTION_BUTTON_DESCRIPTION),
+ )
+
+ browserScreen {
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageContent(customTabPage.content)
+ verifyPageURL(customTabPage.url)
+ }
+
+ customTab {
+ verifyCustomTabActionButton(ACTION_BUTTON_DESCRIPTION)
+ verifyShareButtonIsDisplayed()
+ openCustomTabMenu()
+ verifyTheStandardMenuItems()
+ verifyCustomMenuItem(MENU_ITEM_LABEL)
+ // Close the menu and close the tab
+ mDevice.pressBack()
+ closeCustomTab()
+ assertEquals(Lifecycle.State.DESTROYED, customTabActivity.state)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openCustomTabInFocusTest() {
+ val customTabPage = getGenericTabAsset(webServer, 1)
+
+ launchActivity(createCustomTabIntent(customTabPage.url))
+ customTab {
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageURL(customTabPage.url)
+ openCustomTabMenu()
+ }.clickOpenInFocusButton {
+ verifyPageURL(customTabPage.url)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun customTabNavigationButtonsTest() {
+ val firstPage = getGenericTabAsset(webServer, 1)
+ val secondPage = getGenericTabAsset(webServer, 2)
+
+ launchActivity(createCustomTabIntent(firstPage.url))
+ customTab {
+ verifyPageContent(firstPage.content)
+ clickLinkMatchingText("Tab 2")
+ verifyPageURL(secondPage.url)
+ }.openCustomTabMenu {
+ }.pressBack {
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageURL(firstPage.url)
+ }.openMainMenu {
+ }.pressForward {
+ verifyPageURL(secondPage.url)
+ }.openMainMenu {
+ }.clickReloadButton {
+ verifyPageContent(secondPage.content)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/DownloadFileTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/DownloadFileTest.kt
new file mode 100644
index 0000000000..0d3d52abac
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/DownloadFileTest.kt
@@ -0,0 +1,200 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.downloadRobot
+import org.mozilla.focus.activity.robots.notificationTray
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.DeleteFilesHelper.deleteFileUsingDisplayName
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityIntentsTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.StringsHelper.GOOGLE_PHOTOS
+import org.mozilla.focus.helpers.TestAssetHelper.getImageTestAsset
+import org.mozilla.focus.helpers.TestHelper.assertNativeAppOpens
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.permAllowBtn
+import org.mozilla.focus.helpers.TestHelper.verifyDownloadedFileOnStorage
+import org.mozilla.focus.helpers.TestHelper.verifySnackBarText
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+import java.io.IOException
+
+@RunWith(AndroidJUnit4ClassRunner::class)
+class DownloadFileTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+ private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
+ private var downloadFileName: String = ""
+
+ @get:Rule
+ var mActivityTestRule = MainActivityIntentsTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ deleteFileUsingDisplayName(getTargetContext.applicationContext, downloadFileName)
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun downloadNotificationTest() {
+ val downloadPageUrl = getImageTestAsset(webServer).url
+ downloadFileName = "download.jpg"
+
+ notificationTray {
+ mDevice.openNotification()
+ clearNotifications()
+ }
+
+ // Load website with service worker
+ searchScreen {
+ }.loadPage(downloadPageUrl) { }
+
+ downloadRobot {
+ clickDownloadIconAsset()
+ // If permission dialog appears, grant it
+ if (permAllowBtn.waitForExists(waitingTime)) {
+ permAllowBtn.click()
+ }
+ verifyDownloadDialog(downloadFileName)
+ clickDownloadButton()
+ verifySnackBarText("finished")
+ mDevice.openNotification()
+ notificationTray {
+ verifyDownloadNotification("Download completed", downloadFileName)
+ }
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun cancelDownloadTest() {
+ val downloadPageUrl = getImageTestAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(downloadPageUrl) { }
+
+ downloadRobot {
+ clickDownloadIconAsset()
+ // If permission dialog appears, grant it
+ if (permAllowBtn.waitForExists(waitingTime)) {
+ permAllowBtn.click()
+ }
+ clickCancelDownloadButton()
+ verifyDownloadDialogGone()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun downloadAndOpenJpgFileTest() {
+ val downloadPageUrl = getImageTestAsset(webServer).url
+ downloadFileName = "download.jpg"
+
+ // Load website with service worker
+ searchScreen {
+ }.loadPage(downloadPageUrl) { }
+
+ downloadRobot {
+ clickDownloadIconAsset()
+ // If permission dialog appears on devices with API<30, grant it
+ if (permAllowBtn.waitForExists(waitingTime)) {
+ permAllowBtn.click()
+ }
+ verifyDownloadDialog(downloadFileName)
+ clickDownloadButton()
+ verifyDownloadConfirmationMessage(downloadFileName)
+ openDownloadedFile()
+ assertNativeAppOpens(GOOGLE_PHOTOS)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openPdfFileTest() {
+ downloadFileName = "washington.pdf"
+ val pdfFileURL = "https://storage.googleapis.com/mobile_test_assets/public/washington.pdf"
+ val pdfFileContent = "Washington Crossing the Delaware"
+ searchScreen {
+ }.loadPage(downloadTestPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickLinkMatchingText(downloadFileName)
+ verifyPageURL(pdfFileURL)
+ verifyPageContent(pdfFileContent)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun downloadAndOpenWebmFileTest() {
+ downloadFileName = "videoSample.webm"
+
+ searchScreen {
+ }.loadPage(downloadTestPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickLinkMatchingText(downloadFileName)
+ }
+ // If permission dialog appears on devices with API<30, grant it
+ if (permAllowBtn.waitForExists(waitingTime)) {
+ permAllowBtn.click()
+ }
+ downloadRobot {
+ verifyDownloadDialog(downloadFileName)
+ clickDownloadButton()
+ verifyDownloadConfirmationMessage(downloadFileName)
+ openDownloadedFile()
+ assertNativeAppOpens(GOOGLE_PHOTOS)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyDownloadedFileOnStorageTest() {
+ downloadFileName = "textfile.txt"
+
+ searchScreen {
+ }.loadPage(downloadTestPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickLinkMatchingText(downloadFileName)
+ }
+ // If permission dialog appears on devices with API<30, grant it
+ if (permAllowBtn.waitForExists(waitingTime)) {
+ permAllowBtn.click()
+ }
+ downloadRobot {
+ verifyDownloadDialog(downloadFileName)
+ clickDownloadButton()
+ verifyDownloadConfirmationMessage(downloadFileName)
+ verifyDownloadedFileOnStorage(downloadFileName)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EnhancedTrackingProtectionSettingsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EnhancedTrackingProtectionSettingsTest.kt
new file mode 100644
index 0000000000..c8fc44db06
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EnhancedTrackingProtectionSettingsTest.kt
@@ -0,0 +1,351 @@
+/* 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.focus.activity
+
+import androidx.test.espresso.Espresso.pressBack
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getEnhancedTrackingProtectionAsset
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericAsset
+import org.mozilla.focus.helpers.TestHelper.exitToBrowser
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+import java.io.IOException
+
+@RunWith(AndroidJUnit4ClassRunner::class)
+class EnhancedTrackingProtectionSettingsTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun trackingProtectionTogglesListTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ verifyBlockAdTrackersEnabled(true)
+ verifyBlockAnalyticTrackersEnabled(true)
+ verifyBlockSocialTrackersEnabled(true)
+ verifyBlockOtherTrackersEnabled(false)
+ }
+ }
+
+ // Some workarounds are temp needed, because of https://bugzilla.mozilla.org/show_bug.cgi?id=1794130:
+ // going to the Settings screen,
+ // loading another page,
+ // or refreshing the page multiple times until ETP starts working.
+ @SmokeTest
+ @Test
+ fun blockAdTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "adsTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ }.openMainMenu {
+ }.openSettings {
+ exitToBrowser()
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyTrackingProtectionAlert("ads trackers blocked")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun allowAdTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "adsTrackers")
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ clickAdTrackersBlockSwitch()
+ verifyBlockAdTrackersEnabled(false)
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent("ads trackers not blocked")
+ }
+ }
+
+ // Some workarounds are temp needed, because of https://bugzilla.mozilla.org/show_bug.cgi?id=1794130:
+ // going to the Settings screen,
+ // loading another page,
+ // or refreshing the page multiple times until ETP starts working.
+ @SmokeTest
+ @Test
+ fun blockAnalyticsTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "analyticsTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ }.openMainMenu {
+ }.openSettings {
+ exitToBrowser()
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyTrackingProtectionAlert("analytics trackers blocked")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun allowAnalyticsTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "analyticsTrackers")
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ clickAnalyticsTrackersBlockSwitch()
+ verifyBlockAnalyticTrackersEnabled(false)
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent("analytics trackers not blocked")
+ }
+ }
+
+ // Some workarounds are temp needed, because of https://bugzilla.mozilla.org/show_bug.cgi?id=1794130:
+ // going to the Settings screen,
+ // loading another page,
+ // or refreshing the page multiple times until ETP starts working.
+ @SmokeTest
+ @Test
+ fun blockSocialTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "socialTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ }.openMainMenu {
+ }.openSettings {
+ exitToBrowser()
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyTrackingProtectionAlert("social trackers blocked")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun allowSocialTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "socialTrackers")
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ clickSocialTrackersBlockSwitch()
+ verifyBlockSocialTrackersEnabled(false)
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent("social trackers not blocked")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun allowOtherContentTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "otherTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ pressBack()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent("other content trackers not blocked")
+ }
+ }
+
+ // Some workarounds are temp needed, because of https://bugzilla.mozilla.org/show_bug.cgi?id=1794130:
+ // going to the Settings screen,
+ // loading another page,
+ // or refreshing the page multiple times until ETP starts working.
+ @SmokeTest
+ @Test
+ fun blockOtherContentTrackersTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "otherTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ // loading a generic page to allow GV to fully load on first run
+ verifyPageContent(genericPage.content)
+ pressBack()
+ }
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ clickOtherContentTrackersBlockSwitch()
+ verifyBlockOtherTrackersEnabled(true)
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(trackingPage.url) {
+ verifyTrackingProtectionAlert("other content trackers blocked")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun addURLToTPExceptionsListTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "otherTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ verifyPageContent(genericPage.content)
+ }.openSearchBar {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent(trackingPage.content)
+ }.openSiteSecurityInfoSheet {
+ }.clickTrackingProtectionSwitch {
+ progressBar.waitUntilGone(waitingTime)
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ openExceptionsList()
+ verifyExceptionURL(webServer.hostName)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun removeOneExceptionURLTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "otherTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ verifyPageContent(genericPage.content)
+ }.openSearchBar {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent(trackingPage.content)
+ }.openSiteSecurityInfoSheet {
+ }.clickTrackingProtectionSwitch {
+ progressBar.waitUntilGone(waitingTime)
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ openExceptionsList()
+ removeException()
+ verifyExceptionsListDisabled()
+ exitToBrowser()
+ }
+ browserScreen {
+ }.openSiteSecurityInfoSheet {
+ verifyTrackingProtectionIsEnabled(true)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun removeAllExceptionURLTest() {
+ val genericPage = getGenericAsset(webServer)
+ val trackingPage = getEnhancedTrackingProtectionAsset(webServer, "otherTrackers")
+
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ verifyPageContent(genericPage.content)
+ }.openSearchBar {
+ }.loadPage(trackingPage.url) {
+ verifyPageContent(trackingPage.content)
+ }.openSiteSecurityInfoSheet {
+ }.clickTrackingProtectionSwitch {
+ progressBar.waitUntilGone(waitingTime)
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ openExceptionsList()
+ removeAllExceptions()
+ verifyExceptionsListDisabled()
+ exitToBrowser()
+ }
+ browserScreen {
+ }.openSiteSecurityInfoSheet {
+ verifyTrackingProtectionIsEnabled(true)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EraseBrowsingDataTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EraseBrowsingDataTest.kt
new file mode 100644
index 0000000000..c2c011a7f1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/EraseBrowsingDataTest.kt
@@ -0,0 +1,160 @@
+/* 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.focus.activity
+
+import android.content.Intent
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.notificationTray
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericTabAsset
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.pressHomeKey
+import org.mozilla.focus.helpers.TestHelper.restartApp
+import org.mozilla.focus.helpers.TestHelper.verifySnackBarText
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// These tests verify interaction with the browsing notification and erasing browsing data
+@RunWith(AndroidJUnit4ClassRunner::class)
+class EraseBrowsingDataTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun setUp() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun trashButtonTest() {
+ val testPage = getGenericTabAsset(webServer, 1)
+
+ searchScreen {
+ }.loadPage(testPage.url) {
+ verifyPageContent(testPage.content)
+ // Press erase button, and check for message and return to the main page
+ }.clearBrowsingData {
+ verifySnackBarText(getStringResource(R.string.feedback_erase2))
+ verifyEmptySearchBar()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun notificationEraseAndOpenButtonTest() {
+ val testPage = getGenericTabAsset(webServer, 1)
+
+ notificationTray {
+ mDevice.openNotification()
+ clearNotifications()
+ }
+
+ searchScreen {
+ }.loadPage(testPage.url) { }
+ // Send app to background
+ pressHomeKey()
+ // Pull down system bar and select Erase and Open
+ mDevice.openNotification()
+ notificationTray {
+ verifySystemNotificationExists(getStringResource(R.string.notification_erase_text))
+ expandEraseBrowsingNotification()
+ }.clickEraseAndOpenNotificationButton {
+ verifySnackBarText(getStringResource(R.string.feedback_erase2))
+ verifyEmptySearchBar()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun deleteHistoryOnRestartTest() {
+ val testPage = getGenericTabAsset(webServer, 1)
+
+ searchScreen {
+ }.loadPage(testPage.url) {}
+ restartApp(mActivityTestRule)
+ homeScreen {
+ verifyEmptySearchBar()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun systemBarHomeViewTest() {
+ val testPage = getGenericTabAsset(webServer, 1)
+ val LAUNCH_TIMEOUT = 5000
+ val launcherPackage = mDevice.launcherPackageName
+
+ notificationTray {
+ mDevice.openNotification()
+ clearNotifications()
+ }
+
+ // Leave Focus open, delete browsing history and check the app is still running
+ searchScreen {
+ }.loadPage(testPage.url) { }
+ mDevice.openNotification()
+ notificationTray {
+ verifySystemNotificationExists(getStringResource(R.string.notification_erase_text))
+ expandEraseBrowsingNotification()
+ }.clickNotificationMessage {
+ verifyEmptySearchBar()
+ }
+
+ // Switch out of Focus, delete browsing history and check the app is killed
+ searchScreen {
+ }.loadPage(testPage.url) { }
+ pressHomeKey()
+ mDevice.openNotification()
+ notificationTray {
+ verifySystemNotificationExists(getStringResource(R.string.notification_erase_text))
+ expandEraseBrowsingNotification()
+ }.clickNotificationMessage {
+ // Wait for launcher
+ Assert.assertNotNull(launcherPackage)
+ mDevice.wait(
+ Until.hasObject(By.pkg(launcherPackage).depth(0)),
+ LAUNCH_TIMEOUT.toLong(),
+ )
+
+ // Re-launch the app, verify it's not showing the previous browsing session
+ mActivityTestRule.launchActivity(Intent(Intent.ACTION_MAIN))
+ verifyEmptySearchBar()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ErrorPagesTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ErrorPagesTest.kt
new file mode 100644
index 0000000000..eb511ff63d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ErrorPagesTest.kt
@@ -0,0 +1,60 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.setNetworkEnabled
+
+// This tests verify invalid URL and no network connection error pages
+@RunWith(AndroidJUnit4ClassRunner::class)
+class ErrorPagesTest {
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @Test
+ fun badURLCheckTest() {
+ val badURl = "bad.url"
+
+ searchScreen {
+ }.loadPage(badURl) {
+ verifyPageContent(getStringResource(R.string.mozac_browser_errorpages_unknown_host_title))
+ verifyPageContent("Try Again")
+ }
+ }
+
+ @Test
+ fun noNetworkConnectionErrorPageTest() {
+ val pageUrl = "mozilla.org"
+
+ setNetworkEnabled(false)
+ searchScreen {
+ }.loadPage(pageUrl) {
+ verifyPageContent(getStringResource(R.string.mozac_browser_errorpages_unknown_host_title))
+ verifyPageContent("Try Again")
+ setNetworkEnabled(true)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/FirstRunTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/FirstRunTest.kt
new file mode 100644
index 0000000000..e110d1035c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/FirstRunTest.kt
@@ -0,0 +1,57 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestHelper.restartApp
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// Tests the First run onboarding screens
+@RunWith(AndroidJUnit4ClassRunner::class)
+class FirstRunTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = true)
+
+ @Before
+ fun startWebServer() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ }
+
+ @After
+ fun stopWebServer() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun onboardingScreensTest() {
+ homeScreen {
+ verifyFirstOnboardingScreenItems()
+ restartApp(mActivityTestRule)
+ verifyFirstOnboardingScreenItems()
+ clickGetStartedButton()
+ verifySecondOnboardingScreenItems()
+ restartApp(mActivityTestRule)
+ verifySecondOnboardingScreenItems()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MediaPlaybackTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MediaPlaybackTest.kt
new file mode 100644
index 0000000000..c8129b9490
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MediaPlaybackTest.kt
@@ -0,0 +1,101 @@
+/* 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.focus.activity
+
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.notificationTray
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getMediaTestAsset
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+class MediaPlaybackTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get:Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun testVideoPlayback() {
+ val videoPageUrl = getMediaTestAsset(webServer, "videoPage").url
+
+ searchScreen {
+ }.loadPage(videoPageUrl) {
+ clickPlayButton()
+ waitForPlaybackToStart()
+ // need this alert hack to check the video is playing,
+ // currently the test cannot verify the text in the page
+ clickPauseButton()
+ verifyPlaybackStopped()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun testAudioPlayback() {
+ val audioPageUrl = getMediaTestAsset(webServer, "audioPage").url
+
+ searchScreen {
+ }.loadPage(audioPageUrl) {
+ clickPlayButton()
+ waitForPlaybackToStart()
+ // need this alert hack to check the video is playing,
+ // currently the test cannot verify the text in the page
+ clickPauseButton()
+ verifyPlaybackStopped()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun testMediaContentNotification() {
+ val audioPageUrl = getMediaTestAsset(webServer, "audioPage").url
+ val notificationMessage = "A site is playing media"
+
+ searchScreen {
+ }.loadPage(audioPageUrl) {
+ clickPlayButton()
+ waitForPlaybackToStart()
+ }
+ mDevice.openNotification()
+ notificationTray {
+ verifyMediaNotificationExists("A site is playing media")
+ clickMediaNotificationControlButton("Pause")
+ verifyMediaNotificationButtonState("Play")
+ }
+ mDevice.pressBack()
+ browserScreen {
+ verifyPlaybackStopped()
+ }.clearBrowsingData {}
+ mDevice.openNotification()
+ notificationTray {
+ verifyNotificationGone(notificationMessage)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MozillaSupportPagesTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MozillaSupportPagesTest.kt
new file mode 100644
index 0000000000..2098180ef4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MozillaSupportPagesTest.kt
@@ -0,0 +1,128 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// This test visits each About page and checks whether some essential elements are being displayed
+@RunWith(AndroidJUnit4ClassRunner::class)
+class MozillaSupportPagesTest {
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun openMenuHelpPageTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.clickHelpPageLink {
+ verifyPageURL("what-firefox-focus-android")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openAboutPageTest() {
+ // Go to settings "About" page
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ }.openAboutPage {
+ verifyVersionNumbers()
+ }.openAboutPageLearnMoreLink {
+ verifyPageURL("www.mozilla.org/en-US/about/manifesto/")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openMozillaSettingsHelpLinkTest() {
+ // Go to settings "About" page
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ }.openHelpLink {
+ verifyPageURL("what-firefox-focus-android")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openYourRightsPageTest() {
+ val yourRightsString = getTargetContext.getString(
+ R.string.your_rights_content1,
+ getTargetContext.getString(R.string.app_name),
+ "Mozilla Public License",
+ )
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ }.openYourRightsPage {
+ verifyPageContent(yourRightsString)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openLibrariesThatWeUse() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ }.openLibrariesUsedPage {
+ verifyLibrariesUsedTitle()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openAboutLicenses() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ }.openLicenseInformation {
+ verifyPageURL("about:license")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun openPrivacyNoticeTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ }.openPrivacyNotice {
+ verifyPageURL("privacy/firefox-focus")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MultitaskingTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MultitaskingTest.kt
new file mode 100644
index 0000000000..061f44d9a3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/MultitaskingTest.kt
@@ -0,0 +1,147 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import androidx.test.platform.app.InstrumentationRegistry
+import mozilla.components.browser.state.selector.privateTabs
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+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.focus.R
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericAsset
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericTabAsset
+import org.mozilla.focus.helpers.TestHelper.clickSnackBarActionButton
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.openAppFromExternalLink
+import org.mozilla.focus.helpers.TestHelper.verifySnackBarText
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+/**
+ * Open multiple sessions and verify that the trash icon changes to a tabs counter
+ */
+@RunWith(AndroidJUnit4ClassRunner::class)
+class MultitaskingTest {
+ private lateinit var webServer: MockWebServer
+ private val store = InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .applicationContext
+ .components
+ .store
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ @Throws(Exception::class)
+ fun startWebServer() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ @Throws(Exception::class)
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun testVisitingMultipleSites() {
+ val tab1 = getGenericTabAsset(webServer, 1)
+ val tab2 = getGenericTabAsset(webServer, 2)
+ val tab3 = getGenericTabAsset(webServer, 3)
+ val eraseBrowsingSnackBarText = getStringResource(R.string.feedback_erase2)
+ val customTabPage = getGenericAsset(webServer)
+
+ // Load website: Erase button visible, Tabs button not
+ searchScreen {
+ }.loadPage(tab1.url) {
+ longPressLink("Tab 2")
+ verifyLinkContextMenu(tab2.url)
+ openLinkInNewTab()
+ verifyNumberOfTabsOpened(2)
+ longPressLink("Tab 3")
+ openLinkInNewTab()
+ verifySnackBarText("New private tab opened")
+ clickSnackBarActionButton("SWITCH")
+ verifyNumberOfTabsOpened(3)
+ }
+
+ openAppFromExternalLink(customTabPage.url)
+ browserScreen {
+ verifyNumberOfTabsOpened(4)
+ }.openTabsTray {
+ verifyTabsOrder(tab1.title, tab3.title, tab2.title, customTabPage.title)
+ }.selectTab(tab1.title) {
+ verifyPageContent("Tab 1")
+ }.clearBrowsingData {
+ verifySnackBarText(eraseBrowsingSnackBarText)
+ assertTrue(store.state.privateTabs.isEmpty())
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun closeTabButtonTest() {
+ val tab1 = getGenericTabAsset(webServer, 1)
+ val tab2 = getGenericTabAsset(webServer, 2)
+ val tab3 = getGenericTabAsset(webServer, 3)
+
+ searchScreen {
+ }.loadPage(tab1.url) {
+ verifyPageContent("Tab 1")
+ longPressLink("Tab 2")
+ openLinkInNewTab()
+ longPressLink("Tab 3")
+ openLinkInNewTab()
+ verifyNumberOfTabsOpened(3)
+ }.openTabsTray {
+ verifyTabsOrder(tab1.title, tab3.title, tab2.title)
+ }.closeTab(tab1.title) {
+ }.openTabsTray {
+ verifyTabsOrder(tab3.title, tab2.title)
+ }.closeTab(tab3.title) {
+ verifyTabsCounterNotShown()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyTabsTrayListTest() {
+ val tab1 = getGenericTabAsset(webServer, 1)
+ val tab2 = getGenericTabAsset(webServer, 2)
+
+ searchScreen {
+ }.loadPage(tab1.url) {
+ longPressLink("Tab 2")
+ openLinkInNewTab()
+ }.openTabsTray {
+ }.selectTab(tab2.title) {
+ }.openTabsTray {
+ verifyCloseTabButton(tab1.title)
+ verifyCloseTabButton(tab2.title)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OldFirstRunTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OldFirstRunTest.kt
new file mode 100644
index 0000000000..8bb0ec78db
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OldFirstRunTest.kt
@@ -0,0 +1,67 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+
+// Tests the First run onboarding screens
+@RunWith(AndroidJUnit4ClassRunner::class)
+class OldFirstRunTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = true, showNewOnboarding = false)
+
+ @Before
+ fun setUp() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @Test
+ fun firstRunOnboardingTest() {
+ homeScreen {
+ verifyOnboardingFirstSlide()
+ clickOnboardingNextButton()
+ verifyOnboardingSecondSlide()
+ clickOnboardingNextButton()
+ verifyOnboardingThirdSlide()
+ clickOnboardingNextButton()
+ verifyOnboardingLastSlide()
+ clickOnboardingFinishButton()
+ verifyEmptySearchBar()
+ }
+ }
+
+ @Test
+ fun skipFirstRunOnboardingTest() {
+ homeScreen {
+ verifyOnboardingFirstSlide()
+ clickOnboardingNextButton()
+ verifyOnboardingSecondSlide()
+ skipFirstRun()
+ verifyEmptySearchBar()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OpenInExternalBrowserDialogueTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OpenInExternalBrowserDialogueTest.kt
new file mode 100644
index 0000000000..59df011f65
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/OpenInExternalBrowserDialogueTest.kt
@@ -0,0 +1,61 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityIntentsTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.StringsHelper.GOOGLE_CHROME
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericAsset
+import org.mozilla.focus.helpers.TestHelper.assertNativeAppOpens
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// This test verifies the "Open in..." option from the main menu
+@RunWith(AndroidJUnit4ClassRunner::class)
+class OpenInExternalBrowserDialogueTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityIntentsTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ mActivityTestRule.activity.finishAndRemoveTask()
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun openPageInExternalAppTest() {
+ val pageUrl = getGenericAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ }.openMainMenu {
+ clickOpenInOption()
+ verifyOpenInDialog()
+ clickOpenInChrome()
+ assertNativeAppOpens(GOOGLE_CHROME)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SafeBrowsingTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SafeBrowsingTest.kt
new file mode 100644
index 0000000000..4843da7d24
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SafeBrowsingTest.kt
@@ -0,0 +1,139 @@
+/* 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.focus.activity
+
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// These tests verify the Safe Browsing feature by visiting unsafe URLs and checking they are blocked
+class SafeBrowsingTest {
+ private lateinit var webServer: MockWebServer
+ private val malwareWarning = getStringResource(R.string.mozac_browser_errorpages_safe_browsing_malware_uri_title)
+ private val phishingWarning = getStringResource(R.string.mozac_browser_errorpages_safe_phishing_uri_title)
+ private val unwantedSoftwareWarning =
+ getStringResource(R.string.mozac_browser_errorpages_safe_browsing_unwanted_uri_title)
+ private val harmfulSiteWarning = getStringResource(R.string.mozac_browser_errorpages_safe_harmful_uri_title)
+ private val tryAgainButton = getStringResource(R.string.mozac_browser_errorpages_page_refresh)
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun blockMalwarePageTest() {
+ val malwareURl = "http://itisatrap.org/firefox/its-an-attack.html"
+
+ searchScreen {
+ }.loadPage(malwareURl) {
+ verifyPageContent(malwareWarning)
+ verifyPageContent(tryAgainButton)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun blockPhishingPageTest() {
+ val phishingURl = "http://itisatrap.org/firefox/its-a-trap.html"
+
+ searchScreen {
+ }.loadPage(phishingURl) {
+ verifyPageContent(phishingWarning)
+ verifyPageContent(tryAgainButton)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun blockUnwantedSoftwarePageTest() {
+ val unwantedURl = "http://itisatrap.org/firefox/unwanted.html"
+
+ searchScreen {
+ }.loadPage(unwantedURl) {
+ verifyPageContent(unwantedSoftwareWarning)
+ verifyPageContent(tryAgainButton)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun blockHarmfulPageTest() {
+ val harmfulURl = "https://www.itisatrap.org/firefox/harmful.html"
+
+ searchScreen {
+ }.loadPage(harmfulURl) {
+ verifyPageContent(harmfulSiteWarning)
+ verifyPageContent(tryAgainButton)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun unblockSafeBrowsingTest() {
+ val malwareURl = "http://itisatrap.org/firefox/its-an-attack.html"
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ switchSafeBrowsingToggle()
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(malwareURl) {
+ verifyPageContent("It’s an Attack!")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyPageSecurityIconAndInfo() {
+ val safePageUrl = "https://mozilla-mobile.github.io/testapp/"
+ val insecurePageUrl = "http://itisatrap.org/firefox/its-a-trap.html"
+
+ searchScreen {
+ }.loadPage(safePageUrl) {
+ verifyPageContent("Lets test!")
+ verifySiteTrackingProtectionIconShown()
+ }.openSiteSecurityInfoSheet {
+ verifySiteConnectionInfoIsSecure(true)
+ }.closeSecurityInfoSheet {
+ }.clearBrowsingData {}
+ searchScreen {
+ }.loadPage(insecurePageUrl) {
+ verifyPageURL(insecurePageUrl)
+ verifySiteSecurityIndicatorShown()
+ }.openSiteSecurityInfoSheet {
+ verifySiteConnectionInfoIsSecure(false)
+ }.closeSecurityInfoSheet { }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SearchTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SearchTest.kt
new file mode 100644
index 0000000000..82b7384965
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SearchTest.kt
@@ -0,0 +1,184 @@
+/* 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.focus.activity
+
+import androidx.test.espresso.Espresso.pressBack
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.pressEnterKey
+import org.mozilla.focus.helpers.TestHelper.verifyKeyboardVisibility
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// This test checks the search engine can be changed and that search suggestions appear
+class SearchTest {
+ private lateinit var searchString: String
+ private val enginesList = listOf("DuckDuckGo", "Google", "Amazon.com", "Wikipedia")
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun changeSearchEngineTest() {
+ for (searchEngine in enginesList) {
+ // Open [settings menu] and select Search engine
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ openSearchEngineSubMenu()
+ selectSearchEngine(searchEngine)
+ exitToTop()
+ }
+
+ searchScreen {
+ typeInSearchBar("mozilla ")
+ pressEnterKey()
+ }
+
+ browserScreen {
+ verifyPageURL(searchEngine)
+ pressBack()
+ }
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun enableSearchSuggestionOnFirstRunTest() {
+ searchString = "mozilla "
+
+ searchScreen {
+ // type and check search suggestions are displayed
+ typeInSearchBar(searchString)
+ allowEnableSearchSuggestions()
+ verifySearchSuggestionsAreShown()
+ clearSearchBar()
+ }
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ verifySearchSuggestionsSwitchState(true)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun disableSearchSuggestionOnFirstRunTest() {
+ searchString = "mozilla "
+
+ searchScreen {
+ typeInSearchBar(searchString)
+ denyEnableSearchSuggestions()
+ verifySearchSuggestionsAreNotShown()
+ clearSearchBar()
+ }
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ verifySearchSuggestionsSwitchState(false)
+ }
+ }
+
+ @Test
+ fun testBlankSearchDoesNothing() {
+ searchScreen {
+ // Search on blank spaces should not do anything
+ typeInSearchBar(" ")
+ pressEnterKey()
+ searchScreen {
+ verifySearchEditBarContainsText(" ")
+ }
+ }
+ }
+
+ @Test
+ fun testSearchBarShowsSearchTermOnEdit() {
+ searchString = "mozilla focus"
+
+ searchScreen {
+ typeInSearchBar(searchString)
+ pressEnterKey()
+ }
+ browserScreen {
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageURL("google")
+ }.openSearchBar {
+ // Tap URL bar, check it displays search term (instead of URL)
+ verifySearchEditBarContainsText(searchString)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun disableSearchSuggestionsTest() {
+ searchString = "mozilla "
+
+ searchScreen {
+ // Search on blank spaces should not do anything
+ verifySearchBarIsDisplayed()
+ typeInSearchBar(searchString)
+ allowEnableSearchSuggestions()
+ clearSearchBar()
+ }
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ clickSearchSuggestionsSwitch()
+ exitToTop()
+ }
+
+ searchScreen {
+ typeInSearchBar(searchString)
+ verifySearchSuggestionsAreNotShown()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun clearSearchButtonTest() {
+ searchString = "mozilla "
+
+ homeScreen {
+ }.openSearchBar {
+ typeInSearchBar(searchString)
+ verifyKeyboardVisibility(true)
+ verifySearchEditBarContainsText(searchString)
+ clearSearchBar()
+ verifyKeyboardVisibility(true)
+ verifySearchEditBarIsEmpty()
+ }
+
+ searchString = "firefox"
+
+ searchScreen {
+ typeInSearchBar(searchString)
+ verifySearchEditBarContainsText(searchString)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsAdvancedTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsAdvancedTest.kt
new file mode 100644
index 0000000000..aa31ed5441
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsAdvancedTest.kt
@@ -0,0 +1,82 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericTabAsset
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// These tests check the advanced settings options
+@RunWith(AndroidJUnit4ClassRunner::class)
+class SettingsAdvancedTest {
+ private lateinit var webServer: MockWebServer
+
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setup() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun openLinksInAppsTest() {
+ val tab3Url = getGenericTabAsset(webServer, 3).url
+ val youtubeLink = "https://www.youtube.com/c/MozillaChannel/videos"
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openAdvancedSettingsMenu {
+ verifyOpenLinksInAppsSwitchState(false)
+ clickOpenLinksInAppsSwitch()
+ verifyOpenLinksInAppsSwitchState(true)
+ }.goBackToSettings {
+ }.goBackToHomeScreen {
+ }.loadPage(tab3Url) {
+ progressBar.waitUntilGone(waitingTimeShort)
+ clickLinkMatchingText("Mozilla Youtube link")
+ verifyOpenLinksInAppsPrompt(true, youtubeLink)
+ clickOpenLinksInAppsCancelButton()
+ }.clearBrowsingData {
+ }.openMainMenu {
+ }.openSettings {
+ }.openAdvancedSettingsMenu {
+ verifyOpenLinksInAppsSwitchState(true)
+ clickOpenLinksInAppsSwitch()
+ verifyOpenLinksInAppsSwitchState(false)
+ }.goBackToSettings {
+ }.goBackToHomeScreen {
+ }.loadPage(tab3Url) {
+ progressBar.waitUntilGone(waitingTimeShort)
+ clickLinkMatchingText("Mozilla Youtube link")
+ verifyOpenLinksInAppsPrompt(false, youtubeLink)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsGeneralTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsGeneralTest.kt
new file mode 100644
index 0000000000..e5b49920d7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsGeneralTest.kt
@@ -0,0 +1,178 @@
+/* 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.focus.activity
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.test.platform.app.InstrumentationRegistry
+import mozilla.components.support.locale.LocaleManager
+import mozilla.components.support.locale.LocaleUseCases
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.helpers.MainActivityIntentsTestRule
+import org.mozilla.focus.helpers.StringsHelper.AF_GENERAL_HEADING
+import org.mozilla.focus.helpers.StringsHelper.AF_HELP
+import org.mozilla.focus.helpers.StringsHelper.AF_LANGUAGE_MENU
+import org.mozilla.focus.helpers.StringsHelper.AF_LANGUAGE_SYSTEM_DEFAULT
+import org.mozilla.focus.helpers.StringsHelper.AF_SETTINGS
+import org.mozilla.focus.helpers.StringsHelper.EN_AFRIKAANS_LOCALE
+import org.mozilla.focus.helpers.StringsHelper.EN_LANGUAGE_MENU_HEADING
+import org.mozilla.focus.helpers.StringsHelper.FR_GENERAL_HEADING
+import org.mozilla.focus.helpers.StringsHelper.FR_LANGUAGE_MENU
+import org.mozilla.focus.helpers.StringsHelper.FR_LANGUAGE_SYSTEM_DEFAULT
+import org.mozilla.focus.helpers.StringsHelper.FR_SETTINGS
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.verifyTranslatedTextExists
+import org.mozilla.focus.locale.Locales
+import org.mozilla.focus.testAnnotations.SmokeTest
+import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
+
+// Tests for the General settings sub-menu: changing theme, locale and default browser
+class SettingsGeneralTest {
+ @get: Rule
+ var mActivityTestRule = MainActivityIntentsTestRule(showFirstRun = false)
+
+ @get: Rule
+ var watcher: TestRule = object : TestWatcher() {
+ override fun starting(description: Description) {
+ println("Starting test: " + description.methodName)
+ if (description.methodName == "frenchLocaleTest") {
+ changeLocale("fr")
+ }
+ }
+ }
+
+ @After
+ fun tearDown() {
+ changeLocale("en")
+ }
+
+ @SmokeTest
+ @Test
+ fun changeThemeTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openGeneralSettingsMenu {
+ verifyThemesList()
+ selectDarkTheme()
+ verifyThemeApplied(isDarkTheme = true, getThemeState = getUiTheme())
+ selectLightTheme()
+ verifyThemeApplied(isLightTheme = true, getThemeState = getUiTheme())
+ selectDeviceTheme()
+ verifyThemeApplied(isLightTheme = true, getThemeState = getUiTheme())
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun englishSystemLocaleTest() {
+ /* Go to Settings and change language to French*/
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openGeneralSettingsMenu {
+ openLanguageSelectionMenu()
+ verifySystemLocaleSelected()
+ selectLanguage(EN_AFRIKAANS_LOCALE)
+ verifyTranslatedTextExists(AF_LANGUAGE_MENU)
+ exitToTop()
+ }
+ /* Exit to main and see the UI is in French as well */
+ homeScreen {
+ }.openMainMenu {
+ verifyTranslatedTextExists(AF_SETTINGS)
+ verifyTranslatedTextExists(AF_HELP)
+ /* change back to system locale, verify the locale is changed */
+ }.openSettings(AF_SETTINGS) {
+ }.openGeneralSettingsMenu(AF_GENERAL_HEADING) {
+ openLanguageSelectionMenu(AF_LANGUAGE_MENU)
+ selectLanguage(AF_LANGUAGE_SYSTEM_DEFAULT)
+ verifyTranslatedTextExists(EN_LANGUAGE_MENU_HEADING)
+ exitToTop()
+ }
+ homeScreen {
+ }.openMainMenu {
+ verifySettingsButtonExists()
+ verifyHelpPageLinkExists()
+ }
+ }
+
+ @Test
+ fun frenchLocaleTest() {
+ /* Go to Settings */
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings(FR_SETTINGS) {
+ }.openGeneralSettingsMenu(FR_GENERAL_HEADING) {
+ openLanguageSelectionMenu(FR_LANGUAGE_MENU)
+ verifySystemLocaleSelected(FR_LANGUAGE_SYSTEM_DEFAULT)
+ /* change locale to English, verify the locale is changed */
+ selectLanguage(EN_AFRIKAANS_LOCALE)
+ verifyTranslatedTextExists(AF_LANGUAGE_MENU)
+ exitToTop()
+ }
+ homeScreen {
+ }.openMainMenu {
+ verifyTranslatedTextExists(AF_SETTINGS)
+ verifyTranslatedTextExists(AF_HELP)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun changeDefaultBrowserTest() {
+ val supportPageUrl = "https://support.mozilla.org/en-US/kb/set-firefox-focus-default-browser-android"
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openGeneralSettingsMenu {
+ clickSetDefaultBrowser()
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
+ browserScreen {
+ verifyPageURL(supportPageUrl)
+ }
+ } else {
+ verifyAndroidDefaultAppsMenuAppears()
+ // for API 24 to 28 we'll skip these steps because the switch doesn't update after
+ // returning from Default apps settings, not reproducing manually
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ selectFocusDefaultBrowser()
+ verifySwitchIsToggled(true)
+ }
+ }
+ }
+ }
+
+ fun changeLocale(languageTag: String) {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val locale = Locales.parseLocaleCode(languageTag)
+ LocaleManager.setNewLocale(
+ mActivityTestRule.activity,
+ LocaleUseCases(context.components.store),
+ locale,
+ )
+ runOnUiThread { mActivityTestRule.activity.recreate() }
+ }
+
+ private fun getUiTheme(): Boolean {
+ val mode =
+ mActivityTestRule.activity.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK)
+
+ return when (mode) {
+ Configuration.UI_MODE_NIGHT_YES -> true // dark theme is set
+ Configuration.UI_MODE_NIGHT_NO -> false // dark theme is not set, using light theme
+ else -> false // default option is light theme
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsPrivacyTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsPrivacyTest.kt
new file mode 100644
index 0000000000..f313eef047
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsPrivacyTest.kt
@@ -0,0 +1,135 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.TestAssetHelper.getStorageTestAsset
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.progressBar
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// These tests the Privacy and Security settings menus and options
+@RunWith(AndroidJUnit4ClassRunner::class)
+class SettingsPrivacyTest {
+ private val featureSettingsHelper = FeatureSettingsHelper()
+ private lateinit var webServer: MockWebServer
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun setup() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ featureSettingsHelper.resetAllFeatureFlags()
+ webServer.shutdown()
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyCookiesAndSiteDataItemsTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ verifyCookiesAndSiteDataSection()
+ clickBlockCookies()
+ verifyBlockCookiesPrompt()
+ clickCancelBlockCookiesPrompt()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyAllCookiesBlockedTest() {
+ val sameSiteCookiesUrl = getStorageTestAsset(webServer, "same-site-cookies.html").url
+ val thirdPartyCookiesUrl = getStorageTestAsset(webServer, "cross-site-cookies.html").url
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ clickBlockCookies()
+ clickYesPleaseOption()
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(sameSiteCookiesUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyCookiesEnabled("BLOCKED")
+ }.clearBrowsingData {
+ }.openSearchBar {
+ }.loadPage(thirdPartyCookiesUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyCookiesEnabled("BLOCKED")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verify3rdPartyCookiesBlockedTest() {
+ val sameSiteCookiesUrl = getStorageTestAsset(webServer, "same-site-cookies.html").url
+ val thirdPartyCookiesURL = getStorageTestAsset(webServer, "cross-site-cookies.html").url
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ clickBlockCookies()
+ clickBlockThirdPartyCookiesOnly()
+ }.goBackToSettings {
+ }.goBackToHomeScreen {
+ }.loadPage(thirdPartyCookiesURL) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyCookiesEnabled("BLOCKED")
+ }.clearBrowsingData {
+ }.openSearchBar {
+ }.loadPage(sameSiteCookiesUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyCookiesEnabled("UNRESTRICTED")
+ }
+ }
+
+ @Test
+ fun verifyCrossSiteCookiesBlockedTest() {
+ val sameSiteCookiesUrl = getStorageTestAsset(webServer, "same-site-cookies.html").url
+ val crossSiteCookiesURL = getStorageTestAsset(webServer, "cross-site-cookies.html").url
+
+ searchScreen {
+ }.loadPage(crossSiteCookiesURL) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyCookiesEnabled("PARTITIONED")
+ }.clearBrowsingData {
+ }.openSearchBar {
+ }.loadPage(sameSiteCookiesUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyCookiesEnabled("UNRESTRICTED")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsTest.kt
new file mode 100644
index 0000000000..83e813c486
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SettingsTest.kt
@@ -0,0 +1,85 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// This test checks all the headings in the Settings menu are there
+@RunWith(AndroidJUnit4ClassRunner::class)
+class SettingsTest {
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @SmokeTest
+ @Test
+ fun accessSettingsMenuTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ verifySettingsMenuItems()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyGeneralSettingsMenuTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openGeneralSettingsMenu {
+ verifyGeneralSettingsItems()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyPrivacySettingsMenuTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ verifyPrivacySettingsItems()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifySearchSettingsMenuTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ verifySearchSettingsItems()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyAdvancedSettingsMenuTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openAdvancedSettingsMenu {
+ verifyAdvancedSettingsItems()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyMozillaMenuTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openMozillaSettingsMenu {
+ verifyMozillaMenuItems()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ShortcutsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ShortcutsTest.kt
new file mode 100644
index 0000000000..3c62d8efeb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ShortcutsTest.kt
@@ -0,0 +1,124 @@
+/* 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/. */
+
+@file:Suppress("DEPRECATION")
+
+package org.mozilla.focus.activity
+
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.robots.browserScreen
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericAsset
+import org.mozilla.focus.testAnnotations.SmokeTest
+import java.io.IOException
+
+class ShortcutsTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun renameShortcutTest() {
+ val webPage = object {
+ val url = getGenericAsset(webServer).url
+ val title = getGenericAsset(webServer).title
+ val content = getGenericAsset(webServer).content
+ val newTitle = "TestShortcut"
+ }
+
+ searchScreen {
+ }.loadPage(webPage.url) {
+ verifyPageContent(webPage.content)
+ }.openMainMenu {
+ clickAddToShortcuts()
+ }
+ browserScreen {
+ }.clearBrowsingData {
+ verifyPageShortcutExists(webPage.title)
+ longTapPageShortcut(webPage.title)
+ clickRenameShortcut()
+ renameShortcutAndSave(webPage.newTitle)
+ verifyPageShortcutExists(webPage.newTitle)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun shortcutsDoNotOpenInNewTabTest() {
+ val tab1 = TestAssetHelper.getGenericTabAsset(webServer, 1)
+ val tab2 = TestAssetHelper.getGenericTabAsset(webServer, 2)
+
+ searchScreen {
+ }.loadPage(tab1.url) {
+ }.openMainMenu {
+ clickAddToShortcuts()
+ }
+ browserScreen {
+ }.clearBrowsingData {
+ verifyPageShortcutExists(tab1.title)
+ }
+
+ searchScreen {
+ }.loadPage(tab2.url) {
+ }.openSearchBar {
+ }
+
+ homeScreen {
+ }.clickPageShortcut(tab1.title) {
+ verifyTabsCounterNotShown()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun searchBarShowsPageShortcutsTest() {
+ val webPage = getGenericAsset(webServer)
+
+ searchScreen {
+ }.loadPage(webPage.url) {
+ verifyPageContent(webPage.content)
+ }.openMainMenu {
+ clickAddToShortcuts()
+ }
+ browserScreen {
+ }.clearBrowsingData {
+ verifyPageShortcutExists(webPage.title)
+ }.clickPageShortcut(webPage.title) {
+ }.openSearchBar {
+ verifySearchSuggestionsContain(webPage.title)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SitePermissionsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SitePermissionsTest.kt
new file mode 100644
index 0000000000..acbbeedbe4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SitePermissionsTest.kt
@@ -0,0 +1,272 @@
+/* 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.focus.activity
+
+import android.Manifest
+import android.content.Context
+import android.hardware.camera2.CameraManager
+import androidx.test.rule.GrantPermissionRule
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockLocationUpdatesRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getGenericAsset
+import org.mozilla.focus.helpers.TestAssetHelper.getMediaTestAsset
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.helpers.TestHelper.grantAppPermission
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+class SitePermissionsTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ /* Test page created and handled by the Mozilla mobile test-eng team */
+ private val permissionsPage = "https://mozilla-mobile.github.io/testapp/permissions"
+ private val testPageSubstring = "https://mozilla-mobile.github.io:443"
+ private val cameraManager = getTargetContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @get:Rule
+ val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ )
+
+ @get: Rule
+ val mockLocationUpdatesRule = MockLocationUpdatesRule()
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @Test
+ fun sitePermissionsSettingsItemsTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ verifySitePermissionsItems()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun autoplayPermissionsSettingsItemsTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ openAutoPlaySettings()
+ verifyAutoplaySection()
+ }
+ }
+
+ // Tests the default autoplay setting: Block audio only on a video with autoplay attribute and not muted
+ @SmokeTest
+ @Test
+ fun blockAudioAutoplayPermissionTest() {
+ val videoPage = getMediaTestAsset(webServer, "videoPage")
+
+ searchScreen {
+ }.loadPage(videoPage.url) {
+ progressBar.waitUntilGone(waitingTime)
+ // an un-muted video won't be able to autoplay with this setting, so we have to press play
+ clickPlayButton()
+ waitForPlaybackToStart()
+ }
+ }
+
+ // Tests the default autoplay setting: Block audio only on a video with autoplay and muted attributes
+ @SmokeTest
+ @Test
+ fun blockAudioAutoplayPermissionOnMutedVideoTest() {
+ val mutedVideoPage = getMediaTestAsset(webServer, "mutedVideoPage")
+
+ searchScreen {
+ }.loadPage(mutedVideoPage.url) {
+ // a muted video will autoplay with this setting
+ waitForPlaybackToStart()
+ }
+ }
+
+ // Tests the autoplay setting: Allow audio and video on a video with autoplay attribute and not muted
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1830312")
+ @SmokeTest
+ @Test
+ fun allowAudioVideoAutoplayPermissionTest() {
+ val videoPage = getMediaTestAsset(webServer, "videoPage")
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ openAutoPlaySettings()
+ selectAllowAudioVideoAutoplay()
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(videoPage.url) {
+ waitForPlaybackToStart()
+ }
+ }
+
+ // Tests the autoplay setting: Allow audio and video on a video with autoplay and muted attributes
+ @SmokeTest
+ @Test
+ fun allowAudioVideoAutoplayPermissionOnMutedVideoTest() {
+ val genericPage = getGenericAsset(webServer)
+ val mutedVideoPage = getMediaTestAsset(webServer, "mutedVideoPage")
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ openAutoPlaySettings()
+ selectAllowAudioVideoAutoplay()
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(genericPage.url) {
+ }.clearBrowsingData {}
+ searchScreen {
+ }.loadPage(mutedVideoPage.url) {
+ waitForPlaybackToStart()
+ }
+ }
+
+ // Tests the autoplay setting: Block audio and video
+ @SmokeTest
+ @Test
+ fun blockAudioVideoAutoplayPermissionTest() {
+ val videoPage = getMediaTestAsset(webServer, "videoPage")
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ openAutoPlaySettings()
+ selectBlockAudioVideoAutoplay()
+ exitToTop()
+ }
+ searchScreen {
+ }.loadPage(videoPage.url) {
+ clickPlayButton()
+ waitForPlaybackToStart()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun cameraPermissionsSettingsItemsTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ openCameraPermissionsSettings()
+ verifyPermissionsStateSettings()
+ verifyAskToAllowChecked()
+ verifyBlockedByAndroidState()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun locationPermissionsSettingsItemsTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openPrivacySettingsMenu {
+ }.clickSitePermissionsSettings {
+ openLocationPermissionsSettings()
+ verifyPermissionsStateSettings()
+ verifyAskToAllowChecked()
+ verifyBlockedByAndroidState()
+ }
+ }
+
+ @Test
+ fun testLocationSharingNotAllowed() {
+ searchScreen {
+ }.loadPage(permissionsPage) {
+ clickGetLocationButton()
+ verifyLocationPermissionPrompt(testPageSubstring)
+ denySitePermissionRequest()
+ verifyPageContent("User denied geolocation prompt")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun testLocationSharingAllowed() {
+ mockLocationUpdatesRule.setMockLocation()
+
+ searchScreen {
+ }.loadPage(permissionsPage) {
+ clickGetLocationButton()
+ verifyLocationPermissionPrompt(testPageSubstring)
+ allowSitePermissionRequest()
+ verifyPageContent("${mockLocationUpdatesRule.latitude}")
+ verifyPageContent("${mockLocationUpdatesRule.longitude}")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun allowCameraPermissionsTest() {
+ Assume.assumeTrue(cameraManager.cameraIdList.isNotEmpty())
+ searchScreen {
+ }.loadPage(permissionsPage) {
+ clickGetCameraButton()
+ grantAppPermission()
+ verifyCameraPermissionPrompt(testPageSubstring)
+ allowSitePermissionRequest()
+ verifyPageContent("Camera allowed")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun denyCameraPermissionsTest() {
+ Assume.assumeTrue(cameraManager.cameraIdList.isNotEmpty())
+ searchScreen {
+ }.loadPage(permissionsPage) {
+ clickGetCameraButton()
+ grantAppPermission()
+ verifyCameraPermissionPrompt(testPageSubstring)
+ denySitePermissionRequest()
+ verifyPageContent("Camera not allowed")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SwitchContextTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SwitchContextTest.kt
new file mode 100644
index 0000000000..8e8c9d71bb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SwitchContextTest.kt
@@ -0,0 +1,132 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Assert
+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.focus.R
+import org.mozilla.focus.activity.robots.notificationTray
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.pressHomeKey
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+import java.io.IOException
+
+// This test switches out of Focus and opens it from the private browsing notification
+@RunWith(AndroidJUnit4ClassRunner::class)
+class SwitchContextTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+
+ notificationTray {
+ mDevice.openNotification()
+ clearNotifications()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun notificationOpenButtonTest() {
+ val testPage = TestAssetHelper.getGenericAsset(webServer)
+
+ searchScreen {
+ }.loadPage(testPage.url) {
+ verifyPageContent(testPage.content)
+ }
+ // Send app to background
+ pressHomeKey()
+ // Pull down system bar and select Open
+ mDevice.openNotification()
+ notificationTray {
+ verifySystemNotificationExists(getStringResource(R.string.notification_erase_text))
+ expandEraseBrowsingNotification()
+ }.clickNotificationOpenButton {
+ verifyBrowserView()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun switchFromSettingsToFocusTest() {
+ // Initialize UiDevice instance
+ val LAUNCH_TIMEOUT = 5000
+ val SETTINGS_APP = "com.android.settings"
+ val settingsApp = mDevice.findObject(
+ UiSelector()
+ .packageName(SETTINGS_APP)
+ .enabled(true),
+ )
+ val launcherPackage = mDevice.launcherPackageName
+ val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
+ val intent = context.packageManager
+ .getLaunchIntentForPackage(SETTINGS_APP)
+ val testPage = TestAssetHelper.getGenericAsset(webServer)
+
+ // Open a webpage
+ searchScreen {
+ }.loadPage(testPage.url) { }
+
+ // Switch out of Focus, open settings app
+ pressHomeKey()
+
+ // Wait for launcher
+ Assert.assertNotNull(launcherPackage)
+ mDevice.wait(
+ Until.hasObject(By.pkg(launcherPackage).depth(0)),
+ LAUNCH_TIMEOUT.toLong(),
+ )
+
+ // Launch the app
+ context.startActivity(intent)
+ settingsApp.waitForExists(waitingTime)
+ assertTrue(settingsApp.exists())
+
+ // switch to Focus
+ mDevice.openNotification()
+ notificationTray {
+ verifySystemNotificationExists(getStringResource(R.string.notification_erase_text))
+ expandEraseBrowsingNotification()
+ }.clickNotificationOpenButton {
+ verifyBrowserView()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ThreeDotMainMenuTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ThreeDotMainMenuTest.kt
new file mode 100644
index 0000000000..d622fb5c9f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/ThreeDotMainMenuTest.kt
@@ -0,0 +1,137 @@
+/* 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.focus.activity
+
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.TestAssetHelper
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+/**
+ * Verifies main menu items on the homescreen and on a browser page.
+ */
+class ThreeDotMainMenuTest {
+ private lateinit var webServer: MockWebServer
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ val mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun startWebServer() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun homeScreenMenuItemsTest() {
+ homeScreen {
+ }.openMainMenu {
+ verifyHelpPageLinkExists()
+ verifySettingsButtonExists()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun browserMenuItemsTest() {
+ val pageUrl = TestAssetHelper.getGenericTabAsset(webServer, 1).url
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ verifyPageContent("Tab 1")
+ }.openMainMenu {
+ verifyShareButtonExists()
+ verifyAddToHomeButtonExists()
+ verifyFindInPageExists()
+ verifyOpenInButtonExists()
+ verifyRequestDesktopSiteExists()
+ verifySettingsButtonExists()
+ verifyReportSiteIssueButtonExists()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun shareTabTest() {
+ val pageUrl = TestAssetHelper.getGenericTabAsset(webServer, 1).url
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ }.openMainMenu {
+ }.openShareScreen {
+ verifyShareAppsListOpened()
+ mDevice.pressBack()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun findInPageTest() {
+ val pageUrl = TestAssetHelper.getGenericTabAsset(webServer, 1).url
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ }.openMainMenu {
+ }.openFindInPage {
+ enterFindInPageQuery("tab")
+ verifyFindNextInPageResult("1/3")
+ verifyFindNextInPageResult("2/3")
+ verifyFindNextInPageResult("3/3")
+ verifyFindPrevInPageResult("1/3")
+ verifyFindPrevInPageResult("3/3")
+ verifyFindPrevInPageResult("2/3")
+ closeFindInPage()
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun switchDesktopModeTest() {
+ val pageUrl = TestAssetHelper.getGenericTabAsset(webServer, 1).url
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageContent("mobile-site")
+ }.openMainMenu {
+ }.switchDesktopSiteMode {
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageContent("desktop-site")
+ }.openMainMenu {
+ verifyRequestDesktopSiteIsEnabled(true)
+ }.switchDesktopSiteMode {
+ verifyPageContent("mobile-site")
+ }.openMainMenu {
+ verifyRequestDesktopSiteIsEnabled(false)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/URLAutocompleteTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/URLAutocompleteTest.kt
new file mode 100644
index 0000000000..4ccec3b0de
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/URLAutocompleteTest.kt
@@ -0,0 +1,147 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.TestHelper.exitToTop
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// Tests url autocompletion and adding custom autocomplete urls
+@RunWith(AndroidJUnit4ClassRunner::class)
+class URLAutocompleteTest {
+ private val searchTerm = "mozilla"
+ private val autocompleteSuggestion = "mozilla.org"
+ private val pageUrl = "https://www.mozilla.org/"
+ private val customURL = "680news.com"
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ // Test the url autocomplete feature with default settings
+ @SmokeTest
+ @Test
+ fun defaultAutoCompleteURLTest() {
+ searchScreen {
+ // type a partial url, and check it autocompletes
+ typeInSearchBar(searchTerm)
+ verifySearchEditBarContainsText(autocompleteSuggestion)
+ clearSearchBar()
+ // type a full url, and check it does not autocomplete
+ typeInSearchBar(pageUrl)
+ verifySearchEditBarContainsText(pageUrl)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun disableTopSitesAutocompleteTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ openUrlAutocompleteSubMenu()
+ toggleTopSitesAutocomplete()
+ exitToTop()
+ }
+
+ searchScreen {
+ typeInSearchBar(searchTerm)
+ verifySearchEditBarContainsText(searchTerm)
+ }
+ }
+
+ // Add custom Url, verify it works, then remove it and check it is no longer autocompleted
+ @Test
+ fun customUrlAutoCompletionTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ // Add custom autocomplete url
+ openUrlAutocompleteSubMenu()
+ openManageSitesSubMenu()
+ openAddCustomUrlDialog()
+ enterCustomUrl(customURL)
+ saveCustomUrl()
+ verifySavedCustomURL(customURL)
+ exitToTop()
+ }
+ // verify the custom url auto-completes
+ searchScreen {
+ typeInSearchBar(customURL.substring(0, 1))
+ verifySearchEditBarContainsText(customURL)
+ clearSearchBar()
+ }
+
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ // remove custom Url
+ openUrlAutocompleteSubMenu()
+ openManageSitesSubMenu()
+ removeCustomUrl()
+ exitToTop()
+ }
+ // verify it is no longer auto-completed
+ searchScreen {
+ typeInSearchBar(customURL.substring(0, 3))
+ verifySearchEditBarContainsText(customURL.substring(0, 3))
+ clearSearchBar()
+ }
+ }
+
+ // Add custom autocompletion site, then disable autocomplete
+ @Test
+ fun disableAutocompleteForCustomSiteTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ openUrlAutocompleteSubMenu()
+ openManageSitesSubMenu()
+ openAddCustomUrlDialog()
+ enterCustomUrl(customURL)
+ saveCustomUrl()
+ verifySavedCustomURL(customURL)
+ mDevice.pressBack()
+ toggleCustomAutocomplete()
+ exitToTop()
+ }
+
+ searchScreen {
+ typeInSearchBar(customURL.substring(0, 3))
+ verifySearchEditBarContainsText(customURL.substring(0, 3))
+ clearSearchBar()
+ }
+ }
+
+ // Verifies the custom Url can't be added twice
+ @Test
+ fun duplicateCustomUrlNotAllowedTest() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ }.openSearchSettingsMenu {
+ openUrlAutocompleteSubMenu()
+ openManageSitesSubMenu()
+ openAddCustomUrlDialog()
+ enterCustomUrl(customURL)
+ saveCustomUrl()
+ verifySavedCustomURL(customURL)
+ openAddCustomUrlDialog()
+ enterCustomUrl(customURL)
+ saveCustomUrl()
+ verifyCustomUrlDialogNotClosed()
+ exitToTop()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt
new file mode 100644
index 0000000000..84a3e68755
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt
@@ -0,0 +1,174 @@
+/* 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.focus.activity
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityIntentsTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.RetryTestRule
+import org.mozilla.focus.helpers.StringsHelper.GMAIL_APP
+import org.mozilla.focus.helpers.StringsHelper.PHONE_APP
+import org.mozilla.focus.helpers.TestAssetHelper
+import org.mozilla.focus.helpers.TestHelper.assertNativeAppOpens
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.testAnnotations.SmokeTest
+
+// These tests check that various web controls work properly
+@RunWith(AndroidJUnit4ClassRunner::class)
+class WebControlsTest {
+ private lateinit var webServer: MockWebServer
+
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityIntentsTestRule(showFirstRun = false)
+
+ @Rule
+ @JvmField
+ val retryTestRule = RetryTestRule(3)
+
+ @Before
+ fun setup() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ webServer.shutdown()
+ featureSettingsHelper.resetAllFeatureFlags()
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyTextInputTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickAndWriteTextInInputBox("wiki")
+ clickSubmitTextInputButton()
+ verifyPageContent("You entered: wiki")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyDropdownMenuTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickDropDownForm()
+ selectDropDownOption("The National")
+ clickSubmitDropDownButton()
+ verifySelectedDropDownOption("The National")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyExternalLinksTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickLinkMatchingText("External link")
+ progressBar.waitUntilGone(waitingTime)
+ verifyPageURL("DuckDuckGo")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun emailLinkTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ clickLinkMatchingText("Email link")
+ clickOpenLinksInAppsOpenButton()
+ assertNativeAppOpens(GMAIL_APP)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun telephoneLinkTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ clickLinkMatchingText("Telephone link")
+ clickOpenLinksInAppsOpenButton()
+ assertNativeAppOpens(PHONE_APP)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyDismissTextSelectionToolbarTest() {
+ val tab1Url = TestAssetHelper.getGenericTabAsset(webServer, 1).url
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(tab1Url) {
+ progressBar.waitUntilGone(waitingTime)
+ }.openSearchBar {
+ }.loadPage(htmlControlsPage) {
+ progressBar.waitUntilGone(waitingTime)
+ longClickText("Copy")
+ }.goToPreviousPage {
+ verifyCopyOptionDoesNotExist()
+ }
+ }
+
+ @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1841006")
+ @SmokeTest
+ @Test
+ fun verifySelectTextTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ progressBar.waitUntilGone(waitingTime)
+ longClickAndCopyText("Copy")
+ clickAndPasteTextInInputBox()
+ clickSubmitTextInputButton()
+ verifyPageContent("You entered: Copy")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun verifyCalendarFormTest() {
+ val htmlControlsPage = TestAssetHelper.getHTMLControlsPageAsset(webServer).url
+
+ searchScreen {
+ }.loadPage(htmlControlsPage) {
+ progressBar.waitUntilGone(waitingTime)
+ clickCalendarForm()
+ selectDate()
+ clickButtonWithText("OK")
+ clickSubmitDateButton()
+ verifySelectedDate()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/AddToHomeScreenRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/AddToHomeScreenRobot.kt
new file mode 100644
index 0000000000..d1e9ec7c8f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/AddToHomeScreenRobot.kt
@@ -0,0 +1,71 @@
+/* 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.focus.activity.robots
+
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class AddToHomeScreenRobot {
+
+ fun handleAddAutomaticallyDialog() {
+ if (addAutomaticallyBtn.waitForExists(waitingTime)) {
+ addAutomaticallyBtn.click()
+ addAutomaticallyBtn.waitUntilGone(waitingTime)
+ }
+ }
+
+ fun addShortcutWithTitle(title: String) {
+ shortcutTitle.waitForExists(waitingTime)
+ shortcutTitle.clearTextField()
+ shortcutTitle.setText(title)
+ addToHSOKBtn.click()
+ }
+
+ fun addShortcutNoTitle() {
+ shortcutTitle.waitForExists(waitingTime)
+ shortcutTitle.clearTextField()
+ addToHSOKBtn.click()
+ }
+
+ class Transition {
+ // Searches a page shortcut on the device homescreen
+ fun searchAndOpenHomeScreenShortcut(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ mDevice.waitForIdle(waitingTime)
+ mDevice.pressHome()
+
+ fun deviceHomeScreen() = UiScrollable(UiSelector().scrollable(true))
+ deviceHomeScreen().setAsHorizontalList()
+
+ fun shortcut() =
+ deviceHomeScreen()
+ .getChildByText(UiSelector().text(title), title, true)
+ shortcut().waitForExists(waitingTime)
+ shortcut().clickAndWaitForNewWindow()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+private val addToHSOKBtn = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/addtohomescreen_dialog_add")
+ .enabled(true),
+)
+
+private val addAutomaticallyBtn = mDevice.findObject(
+ UiSelector()
+ .className("android.widget.Button")
+ .textContains("Add automatically"),
+)
+
+private val shortcutTitle = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/edit_title"),
+)
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt
new file mode 100644
index 0000000000..a817a5642a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt
@@ -0,0 +1,694 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.IdlingRegistry
+import androidx.test.espresso.IdlingResource
+import androidx.test.espresso.ViewInteraction
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.RootMatchers
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiObjectNotFoundException
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import org.hamcrest.Matchers.not
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.Constants.RETRY_COUNT
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.pageLoadingTime
+import org.mozilla.focus.helpers.TestHelper.progressBar
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+import org.mozilla.focus.idlingResources.SessionLoadedIdlingResource
+import java.time.LocalDate
+
+class BrowserRobot {
+
+ private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
+
+ val progressBar =
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/progress"),
+ )
+
+ fun verifyBrowserView() =
+ assertTrue(
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
+ .waitForExists(waitingTime),
+ )
+
+ fun verifyPageContent(expectedText: String) {
+ sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ assertTrue(
+ webPageItemContainingText(expectedText).waitForExists(pageLoadingTime),
+ )
+ break
+ } catch (e: AssertionError) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ refreshPageIfStillLoading(expectedText)
+ }
+ }
+ }
+ }
+ }
+
+ fun verifyTrackingProtectionAlert(expectedText: String) {
+ // Because of https://bugzilla.mozilla.org/show_bug.cgi?id=1794130,
+ // ETP has delays so we need to refresh the page multiple times until ETP starts working.
+ for (i in 1..RETRY_COUNT) {
+ try {
+ assertTrue(
+ webPageItemContainingText(expectedText)
+ .waitForExists(pageLoadingTime),
+ )
+ // close the JavaScript alert
+ mDevice.pressBack()
+ break
+ } catch (e: AssertionError) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ refreshPageIfStillLoading(expectedText)
+ }
+ }
+ }
+ }
+
+ fun refreshPageIfStillLoading(pageContent: String) {
+ browserScreen {
+ }.openMainMenu {
+ when (mDevice.findObject(UiSelector().description("Reload website")).exists()) {
+ true -> ThreeDotMainMenuRobot.Transition().clickReloadButton {}
+ false -> {
+ ThreeDotMainMenuRobot.Transition().clickStopLoadingButton {
+ if (!mDevice.findObject(UiSelector().textContains(pageContent))
+ .waitForExists(waitingTime)
+ ) {
+ browserScreen {
+ }.openMainMenu {
+ }.clickReloadButton {}
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun verifyPageURL(expectedText: String) {
+ browserURLbar.waitForExists(waitingTime)
+ sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ assertTrue(
+ mDevice.findObject(UiSelector().textContains(expectedText))
+ .waitForExists(waitingTime),
+ )
+ }
+ }
+
+ fun clickGetLocationButton() = clickPageObject(webPageItemContainingText("Get Location"))
+
+ fun clickGetCameraButton() = clickPageObject(webPageItemContainingText("Open camera"))
+
+ fun verifyCameraPermissionPrompt(url: String) {
+ assertTrue(
+ mDevice.findObject(UiSelector().text("Allow $url to use your camera?"))
+ .waitForExists(waitingTime),
+ )
+ }
+
+ fun verifyLocationPermissionPrompt(url: String) {
+ assertTrue(
+ mDevice.findObject(UiSelector().text("Allow $url to use your location?"))
+ .waitForExists(waitingTime),
+ )
+ }
+
+ fun allowSitePermissionRequest() {
+ if (permissionAllowBtn.waitForExists(waitingTime)) {
+ permissionAllowBtn.click()
+ }
+ }
+
+ fun denySitePermissionRequest() {
+ if (permissionDenyBtn.waitForExists(waitingTime)) {
+ permissionDenyBtn.click()
+ }
+ }
+
+ fun longPressLink(linkText: String) = longClickPageObject(webPageItemWithText(linkText))
+
+ fun openLinkInNewTab() {
+ mDevice.findObject(
+ UiSelector().textContains("Open link in private tab"),
+ ).waitForExists(waitingTime)
+ openLinkInPrivateTab.perform(click())
+ }
+
+ fun verifyNumberOfTabsOpened(tabsCount: Int) {
+ assertTrue(
+ mDevice.findObject(
+ UiSelector().description("$tabsCount open tabs. Tap to switch tabs."),
+ ).waitForExists(waitingTime),
+ )
+ }
+
+ fun verifyTabsCounterNotShown() {
+ assertFalse(
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/counter_root"))
+ .waitForExists(waitingTimeShort),
+ )
+ }
+
+ fun verifyShareAppsListOpened() =
+ assertTrue(shareAppsList.waitForExists(waitingTime))
+
+ fun clickPlayButton() = clickPageObject(webPageItemWithText("Play"))
+
+ fun clickPauseButton() = clickPageObject(webPageItemWithText("Pause"))
+
+ fun waitForPlaybackToStart() {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ assertTrue(webPageItemWithText("Media file is playing").waitForExists(pageLoadingTime))
+ break
+ } catch (e: AssertionError) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ clickPlayButton()
+ }
+ }
+ }
+
+ // dismiss the js alert
+ mDevice.findObject(UiSelector().textContains("ok")).click()
+ }
+
+ fun verifyPlaybackStopped() {
+ assertTrue(webPageItemWithText("Media file is paused").waitForExists(waitingTime))
+ // dismiss the js alert
+ mDevice.findObject(UiSelector().textContains("ok")).click()
+ }
+
+ fun verifySiteTrackingProtectionIconShown() = assertTrue(securityIcon.waitForExists(waitingTime))
+
+ fun verifySiteSecurityIndicatorShown() = assertTrue(site_security_indicator.waitForExists(waitingTime))
+
+ fun verifyLinkContextMenu(linkAddress: String) {
+ assertTrue(
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/parentPanel"),
+ ).waitForExists(waitingTime),
+ )
+ onView(withId(R.id.titleView)).check(matches(withText(linkAddress)))
+ openLinkInPrivateTab.check(matches(isDisplayed()))
+ copyLink.check(matches(isDisplayed()))
+ shareLink.check(matches(isDisplayed()))
+ }
+
+ fun verifyImageContextMenu(hasLink: Boolean, linkAddress: String) {
+ assertTrue(
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/parentPanel"),
+ ).waitForExists(waitingTime),
+ )
+ onView(withId(R.id.titleView)).check(matches(withText(linkAddress)))
+ if (hasLink) {
+ openLinkInPrivateTab.check(matches(isDisplayed()))
+ downloadLink.check(matches(isDisplayed()))
+ }
+ copyLink.check(matches(isDisplayed()))
+ shareLink.check(matches(isDisplayed()))
+ shareImage.check(matches(isDisplayed()))
+ openImageInNewTab.check(matches(isDisplayed()))
+ saveImage.check(matches(isDisplayed()))
+ copyImageLocation.check(matches(isDisplayed()))
+ }
+
+ fun clickContextMenuCopyLink(): ViewInteraction = copyLink.perform(click())
+
+ fun clickShareImage() =
+ shareImage.inRoot(RootMatchers.isDialog()).check(matches(isDisplayed())).perform(click())
+
+ fun clickShareLink(): ViewInteraction = shareLink.perform(click())
+
+ fun clickCopyImageLocation(): ViewInteraction = copyImageLocation.perform(click())
+
+ fun clickLinkMatchingText(expectedText: String) = clickPageObject(webPageItemContainingText(expectedText))
+
+ fun verifyOpenLinksInAppsPrompt(openLinksInAppsEnabled: Boolean, link: String) = assertOpenLinksInAppsPrompt(openLinksInAppsEnabled, link)
+
+ fun clickOpenLinksInAppsCancelButton() {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ openLinksInAppsCancelButton.click()
+ assertTrue(openLinksInAppsMessage.waitUntilGone(waitingTime))
+
+ break
+ } catch (e: AssertionError) {
+ if (i == RETRY_COUNT) {
+ throw e
+ }
+ }
+ }
+ }
+
+ fun clickOpenLinksInAppsOpenButton() = openLinksInAppsOpenButton.click()
+
+ fun clickDropDownForm() = clickPageObject(webPageItemWithResourceId("dropDown"))
+
+ fun clickCalendarForm() = clickPageObject(webPageItemWithResourceId("calendar"))
+
+ fun selectDate() {
+ mDevice.findObject(UiSelector().resourceId("android:id/month_view")).waitForExists(waitingTime)
+
+ mDevice.findObject(
+ UiSelector()
+ .textContains("$currentDay")
+ .descriptionContains("$currentDay $currentMonth $currentYear"),
+ ).click()
+ }
+
+ fun clickButtonWithText(button: String) = mDevice.findObject(UiSelector().textContains(button)).click()
+
+ fun clickSubmitDateButton() = clickPageObject(webPageItemWithResourceId("submitDate"))
+
+ fun verifySelectedDate() {
+ mDevice.findObject(
+ UiSelector()
+ .textContains("Submit date")
+ .resourceId("submitDate"),
+ ).waitForExists(waitingTime)
+
+ assertTrue(
+ mDevice.findObject(
+ UiSelector()
+ .text("Selected date is: $currentDate"),
+ ).waitForExists(waitingTime),
+ )
+ }
+
+ fun clickAndWriteTextInInputBox(text: String) {
+ clickPageObject(webPageItemWithResourceId("textInput"))
+ setPageObjectText(webPageItemWithResourceId("textInput"), text)
+ }
+
+ fun longPressTextInputBox() = longClickPageObject(webPageItemWithResourceId("textInput"))
+
+ fun longClickText(expectedText: String) = longClickPageObject(webPageItemContainingText(expectedText))
+
+ fun longClickAndCopyText(expectedText: String) {
+ var currentTries = 0
+ while (currentTries++ < 3) {
+ try {
+ longClickPageObject(webPageItemContainingText(expectedText))
+
+ webPageItemContainingText("Copy").waitForExists(waitingTime)
+ mDevice.findObject(By.textContains("Copy")).click()
+
+ break
+ } catch (e: NullPointerException) {
+ browserScreen {
+ }.openMainMenu {
+ }.clickReloadButton {}
+ }
+ }
+ }
+
+ fun verifyCopyOptionDoesNotExist() =
+ assertFalse(mDevice.findObject(UiSelector().textContains("Copy")).waitForExists(waitingTime))
+
+ fun clickAndPasteTextInInputBox() {
+ var currentTries = 0
+ while (currentTries++ < 3) {
+ try {
+ mDevice.findObject(UiSelector().textContains("Paste")).waitForExists(waitingTime)
+ mDevice.findObject(By.textContains("Paste")).click()
+ break
+ } catch (e: NullPointerException) {
+ longPressTextInputBox()
+ }
+ }
+ }
+
+ fun clickSubmitTextInputButton() = clickPageObject(webPageItemWithResourceId("submitInput"))
+
+ fun selectDropDownOption(optionName: String) {
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/customPanel"),
+ ).waitForExists(waitingTime)
+ mDevice.findObject(UiSelector().textContains(optionName)).click()
+ }
+
+ fun clickSubmitDropDownButton() = clickPageObject(webPageItemWithResourceId("submitOption"))
+
+ fun verifySelectedDropDownOption(optionName: String) {
+ mDevice.findObject(
+ UiSelector()
+ .textContains("Submit drop down option")
+ .resourceId("submitOption"),
+ ).waitForExists(waitingTime)
+
+ assertTrue(
+ mDevice.findObject(
+ UiSelector()
+ .text("Selected option is: $optionName"),
+ ).waitForExists(waitingTime),
+ )
+ }
+
+ fun enterFindInPageQuery(expectedText: String) {
+ mDevice.wait(Until.findObject(By.res("$packageName:id/find_in_page_query_text")), waitingTime)
+ findInPageQuery.perform(ViewActions.clearText())
+ mDevice.wait(Until.gone(By.res("$packageName:id/find_in_page_result_text")), waitingTime)
+ findInPageQuery.perform(ViewActions.typeText(expectedText))
+ mDevice.wait(Until.findObject(By.res("$packageName:id/find_in_page_result_text")), waitingTime)
+ }
+
+ fun verifyFindNextInPageResult(ratioCounter: String) {
+ mDevice.wait(Until.findObject(By.text(ratioCounter)), waitingTime)
+ val resultsCounter = mDevice.findObject(By.text(ratioCounter))
+ findInPageResult.check(matches(withText((ratioCounter))))
+ findInPageNextButton.perform(click())
+ resultsCounter.wait(Until.textNotEquals(ratioCounter), waitingTime)
+ }
+
+ fun verifyFindPrevInPageResult(ratioCounter: String) {
+ mDevice.wait(Until.findObject(By.text(ratioCounter)), waitingTime)
+ val resultsCounter = mDevice.findObject(By.text(ratioCounter))
+ findInPageResult.check(matches(withText((ratioCounter))))
+ findInPagePrevButton.perform(click())
+ resultsCounter.wait(Until.textNotEquals(ratioCounter), waitingTime)
+ }
+
+ fun closeFindInPage() {
+ findInPageCloseButton.perform(click())
+ findInPageQuery.check(matches(not(isDisplayed())))
+ }
+
+ fun verifyCookiesEnabled(areCookiesEnabled: String) {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ assertTrue(
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("cookie_message")
+ .childSelector(
+ UiSelector().textContains(areCookiesEnabled),
+ ),
+ ).waitForExists(waitingTime),
+ )
+ break
+ } catch (e: AssertionError) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ refreshPageIfStillLoading(areCookiesEnabled)
+ }
+ }
+ }
+ }
+
+ fun clickSetCookiesButton() = clickPageObject(webPageItemWithResourceId("setCookies"))
+
+ fun clickPageObject(webPageItem: UiObject) {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ webPageItem.also {
+ it.waitForExists(waitingTime)
+ it.click()
+ }
+
+ break
+ } catch (e: UiObjectNotFoundException) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ browserScreen {
+ }.openMainMenu {
+ }.clickReloadButton {
+ progressBar.waitUntilGone(waitingTime)
+ }
+ }
+ }
+ }
+ }
+
+ fun longClickPageObject(webPageItem: UiObject) {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ webPageItem.also {
+ it.waitForExists(waitingTime)
+ it.longClick()
+ }
+
+ break
+ } catch (e: UiObjectNotFoundException) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ browserScreen {
+ }.openMainMenu {
+ }.clickReloadButton {
+ progressBar.waitUntilGone(waitingTime)
+ }
+ }
+ }
+ }
+ }
+
+ private fun setPageObjectText(webPageItem: UiObject, text: String) {
+ for (i in 1..RETRY_COUNT) {
+ try {
+ webPageItem.also {
+ it.waitForExists(waitingTime)
+ it.setText(text)
+ }
+
+ break
+ } catch (e: UiObjectNotFoundException) {
+ if (i == RETRY_COUNT) {
+ throw e
+ } else {
+ browserScreen {
+ }.openMainMenu {
+ }.clickReloadButton {
+ progressBar.waitUntilGone(waitingTime)
+ }
+ }
+ }
+ }
+ }
+
+ class Transition {
+ fun openSearchBar(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
+ browserURLbar.waitForExists(waitingTime)
+ browserURLbar.click()
+
+ SearchRobot().interact()
+ return SearchRobot.Transition()
+ }
+
+ fun clearBrowsingData(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
+ eraseBrowsingButton
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ HomeScreenRobot().interact()
+ return HomeScreenRobot.Transition()
+ }
+
+ fun openMainMenu(interact: ThreeDotMainMenuRobot.() -> Unit): ThreeDotMainMenuRobot.Transition {
+ browserURLbar.waitForExists(waitingTime)
+ mainMenu
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ ThreeDotMainMenuRobot().interact()
+ return ThreeDotMainMenuRobot.Transition()
+ }
+
+ fun openSiteSettingsMenu(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
+ securityIcon.click()
+
+ HomeScreenRobot().interact()
+ return HomeScreenRobot.Transition()
+ }
+
+ fun openSiteSecurityInfoSheet(interact: SiteSecurityInfoSheetRobot.() -> Unit): SiteSecurityInfoSheetRobot.Transition {
+ if (securityIcon.exists()) {
+ securityIcon.click()
+ } else {
+ site_security_indicator.click()
+ }
+
+ SiteSecurityInfoSheetRobot().interact()
+ return SiteSecurityInfoSheetRobot.Transition()
+ }
+
+ fun openTabsTray(interact: TabsTrayRobot.() -> Unit): TabsTrayRobot.Transition {
+ tabsCounter.perform(click())
+
+ TabsTrayRobot().interact()
+ return TabsTrayRobot.Transition()
+ }
+
+ fun goToPreviousPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ mDevice.pressBack()
+ progressBar.waitUntilGone(waitingTime)
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun clickSaveImage(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
+ saveImage.inRoot(RootMatchers.isDialog()).check(matches(isDisplayed())).perform(click())
+
+ DownloadRobot().interact()
+ return DownloadRobot.Transition()
+ }
+ }
+}
+
+fun browserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+}
+
+inline fun runWithIdleRes(ir: IdlingResource?, pendingCheck: () -> Unit) {
+ try {
+ IdlingRegistry.getInstance().register(ir)
+ pendingCheck()
+ } finally {
+ IdlingRegistry.getInstance().unregister(ir)
+ }
+}
+
+private fun assertOpenLinksInAppsPrompt(openLinksInAppsEnabled: Boolean, link: String) {
+ if (openLinksInAppsEnabled) {
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/parentPanel")).waitForExists(waitingTime)
+ assertTrue(openLinksInAppsMessage.waitForExists(waitingTimeShort))
+ assertTrue(openLinksInAppsLink(link).exists())
+ assertTrue(openLinksInAppsCancelButton.waitForExists(waitingTimeShort))
+ assertTrue(openLinksInAppsOpenButton.waitForExists(waitingTimeShort))
+ } else {
+ assertFalse(
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/parentPanel"),
+ ).waitForExists(waitingTimeShort),
+ )
+ }
+}
+
+private fun openLinksInAppsLink(link: String) = mDevice.findObject(UiSelector().textContains(link))
+
+private val browserURLbar = mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view"),
+)
+
+private val eraseBrowsingButton = onView(withContentDescription("Erase browsing history"))
+
+private val tabsCounter = onView(withId(R.id.counter_root))
+
+private val mainMenu = onView(withId(R.id.mozac_browser_toolbar_menu))
+
+private val shareAppsList =
+ mDevice.findObject(UiSelector().resourceId("android:id/resolver_list"))
+
+private val securityIcon =
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/mozac_browser_toolbar_tracking_protection_indicator"),
+ )
+
+private val site_security_indicator =
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/mozac_browser_toolbar_security_indicator"),
+ )
+
+// Link long-tap context menu items
+private val openLinkInPrivateTab = onView(withText("Open link in private tab"))
+
+private val copyLink = onView(withText("Copy link"))
+
+private val shareLink = onView(withText("Share link"))
+
+// Image long-tap context menu items
+private val openImageInNewTab = onView(withText("Open image in new tab"))
+
+private val downloadLink = onView(withText("Download link"))
+
+private val saveImage = onView(withText("Save image"))
+
+private val copyImageLocation = onView(withText("Copy image location"))
+
+private val shareImage = onView(withText("Share image"))
+
+// Find in page toolbar
+private val findInPageQuery = onView(withId(R.id.find_in_page_query_text))
+
+private val findInPageResult = onView(withId(R.id.find_in_page_result_text))
+
+private val findInPageNextButton = onView(withId(R.id.find_in_page_next_btn))
+
+private val findInPagePrevButton = onView(withId(R.id.find_in_page_prev_btn))
+
+private val findInPageCloseButton = onView(withId(R.id.find_in_page_close_btn))
+
+private val openLinksInAppsMessage = mDevice.findObject(UiSelector().resourceId("$packageName:id/alertTitle"))
+
+private val openLinksInAppsCancelButton = mDevice.findObject(UiSelector().textContains("CANCEL"))
+
+private val openLinksInAppsOpenButton =
+ mDevice.findObject(
+ UiSelector()
+ .index(1)
+ .textContains("OPEN")
+ .className("android.widget.Button")
+ .packageName(packageName),
+ )
+
+private val currentDate = LocalDate.now()
+private val currentDay = currentDate.dayOfMonth
+private val currentMonth = currentDate.month
+private val currentYear = currentDate.year
+
+private val permissionAllowBtn = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/allow_button"),
+)
+
+private val permissionDenyBtn = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/deny_button"),
+)
+
+private fun webPageItemContainingText(itemText: String) =
+ mDevice.findObject(UiSelector().textContains(itemText))
+
+private fun webPageItemWithText(itemText: String) =
+ mDevice.findObject(UiSelector().text(itemText))
+
+private fun webPageItemWithResourceId(resourceId: String) =
+ mDevice.findObject(UiSelector().resourceId(resourceId))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/CustomTabRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/CustomTabRobot.kt
new file mode 100644
index 0000000000..8f7c0ea142
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/CustomTabRobot.kt
@@ -0,0 +1,132 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.ViewInteraction
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withSubstring
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiSelector
+import junit.framework.TestCase.assertTrue
+import org.junit.Assert
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.appName
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.idlingResources.SessionLoadedIdlingResource
+
+class CustomTabRobot {
+
+ private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
+
+ val progressBar: UiObject =
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/progress"),
+ )
+
+ fun verifyCustomTabActionButton(buttonDescription: String) {
+ actionButton(buttonDescription).check(matches(isDisplayed()))
+ }
+
+ fun verifyCustomMenuItem(buttonDescription: String) {
+ customMenuItem(buttonDescription).check(matches(isDisplayed()))
+ }
+
+ fun openCustomTabMenu(): ViewInteraction = menuButton.perform(click())
+
+ fun verifyShareButtonIsDisplayed(): ViewInteraction = shareButton.check(matches(isDisplayed()))
+
+ fun verifyTheStandardMenuItems() {
+ onView(withText("Add to Home screen")).check(matches(isDisplayed()))
+ onView(withText("Find in Page")).check(matches(isDisplayed()))
+ onView(withText("Open in…")).check(matches(isDisplayed()))
+ openInFocusButton.check(matches(isDisplayed()))
+ onView(withSubstring("Desktop site")).check(matches(isDisplayed()))
+ // Removed until https://github.com/mozilla-mobile/android-components/issues/10791 is fixed
+ // onView(withText("Report site issue")).check(matches(isDisplayed()))
+ onView(withText("Powered by $appName")).check(matches(isDisplayed()))
+ }
+
+ fun closeCustomTab() {
+ closeCustomTabButton
+ .check(matches(isDisplayed()))
+ .perform(click())
+ }
+
+ fun verifyPageURL(expectedText: String) {
+ sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
+ assertTrue(
+ "Actual url: ${customTabUrl.text}",
+ customTabUrl.text.contains(expectedText),
+ )
+ }
+ }
+
+ fun verifyPageContent(expectedText: String) {
+ val sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
+ .waitForExists(waitingTime)
+
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ Assert.assertTrue(
+ mDevice.findObject(UiSelector().textContains(expectedText))
+ .waitForExists(waitingTime),
+ )
+ }
+ }
+
+ fun clickLinkMatchingText(expectedText: String) {
+ mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
+ mDevice.findObject(UiSelector().textContains(expectedText)).also { it.click() }
+ }
+
+ class Transition {
+ fun clickOpenInFocusButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ openInFocusButton
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openCustomTabMenu(interact: ThreeDotMainMenuRobot.() -> Unit): ThreeDotMainMenuRobot.Transition {
+ menuButton.perform(click())
+
+ ThreeDotMainMenuRobot().interact()
+ return ThreeDotMainMenuRobot.Transition()
+ }
+ }
+}
+
+fun customTab(interact: CustomTabRobot.() -> Unit): CustomTabRobot.Transition {
+ CustomTabRobot().interact()
+ return CustomTabRobot.Transition()
+}
+
+private fun actionButton(description: String) = onView(withContentDescription(description))
+
+private val menuButton = onView(withId(R.id.mozac_browser_toolbar_menu))
+
+private val shareButton = onView(withContentDescription("Share link"))
+
+private fun customMenuItem(description: String) = onView(withText(description))
+
+private val closeCustomTabButton = onView(withContentDescription(R.string.mozac_feature_customtabs_exit_button))
+
+private val openInFocusButton = onView(withText("Open in $appName"))
+
+private val customTabUrl =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view"))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/DownloadRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/DownloadRobot.kt
new file mode 100644
index 0000000000..171782154c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/DownloadRobot.kt
@@ -0,0 +1,96 @@
+/* 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.focus.activity.robots
+
+import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiSelector
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+import org.mozilla.focus.idlingResources.SessionLoadedIdlingResource
+
+class DownloadRobot {
+ fun verifyDownloadDialog(fileName: String) {
+ assertTrue(downloadDialogTitle.waitForExists(waitingTime))
+ assertTrue(downloadCancelBtn.exists())
+ assertTrue(downloadBtn.exists())
+ assertTrue(downloadFileName.text.contains(fileName))
+ }
+
+ fun verifyDownloadDialogGone() = assertTrue(downloadDialogTitle.waitUntilGone(waitingTime))
+
+ fun clickDownloadIconAsset() {
+ val sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ downloadIconAsset.waitForExists(waitingTime)
+ downloadIconAsset.click()
+ }
+ }
+
+ fun clickDownloadButton() {
+ downloadBtn.waitForExists(waitingTime)
+ downloadBtn.click()
+ }
+
+ fun clickCancelDownloadButton() {
+ downloadCancelBtn.waitForExists(waitingTime)
+ downloadCancelBtn.click()
+ }
+
+ fun verifyDownloadConfirmationMessage(fileName: String) {
+ val snackBar = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/snackbar_text"),
+ )
+ snackBar.waitForExists(waitingTimeShort)
+ assertTrue(
+ snackBar.text,
+ snackBar.text.equals("$fileName finished"),
+ )
+ }
+
+ fun openDownloadedFile() {
+ val snackBarButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/snackbar_action"))
+ snackBarButton.waitForExists(waitingTime)
+ snackBarButton.clickAndWaitForNewWindow(waitingTime)
+ }
+
+ class Transition
+}
+
+fun downloadRobot(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
+ DownloadRobot().interact()
+ return DownloadRobot.Transition()
+}
+
+val downloadIconAsset: UiObject = mDevice.findObject(
+ UiSelector()
+ .resourceId("download"),
+)
+
+private val downloadDialogTitle = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/title"),
+)
+
+private val downloadFileName = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/filename"),
+)
+
+private val downloadCancelBtn = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/close_button"),
+)
+
+private val downloadBtn = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/download_button"),
+)
+
+private val downloadNotificationText = getStringResource(R.string.mozac_feature_downloads_completed_notification_text2)
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/HomeScreenRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/HomeScreenRobot.kt
new file mode 100644
index 0000000000..4309cb2e63
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/HomeScreenRobot.kt
@@ -0,0 +1,235 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiSelector
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.appName
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+
+class HomeScreenRobot {
+
+ fun verifyEmptySearchBar() {
+ editURLBar.waitForExists(waitingTime)
+ assertTrue(editURLBar.text.equals(getStringResource(R.string.urlbar_hint)))
+ }
+
+ fun skipFirstRun() = onView(withId(R.id.skip)).perform(click())
+
+ fun closeOnboarding() = onboardingCloseButton.clickAndWaitForNewWindow(waitingTime)
+
+ fun verifyOnboardingFirstSlide() = assertTrue(firstSlideTitle.waitForExists(waitingTime))
+
+ fun verifyOnboardingSecondSlide() = assertTrue(secondSlideTitle.waitForExists(waitingTime))
+
+ fun verifyOnboardingThirdSlide() = assertTrue(thirdSlideTitle.waitForExists(waitingTime))
+
+ fun verifyOnboardingLastSlide() = assertTrue(lastSlide.waitForExists(waitingTime))
+
+ fun clickOnboardingNextButton() = nextButton.clickAndWaitForNewWindow(waitingTimeShort)
+
+ fun clickOnboardingFinishButton() = finishButton.clickAndWaitForNewWindow(waitingTimeShort)
+
+ fun verifyPageShortcutExists(title: String) {
+ assertTrue(
+ topSitesList
+ .getChild(UiSelector().textContains(title))
+ .waitForExists(waitingTime),
+ )
+ }
+
+ fun longTapPageShortcut(title: String) {
+ mDevice.findObject(By.text(title)).click(4000)
+ }
+
+ fun clickRenameShortcut() {
+ mDevice.findObject(UiSelector().text("Rename"))
+ .also {
+ it.waitForExists(waitingTimeShort)
+ it.click()
+ }
+ }
+
+ fun renameShortcutAndSave(newTitle: String) {
+ val titleTextField = mDevice.findObject(UiSelector().className("android.widget.EditText"))
+ val okButton = mDevice.findObject(UiSelector().textContains("ok"))
+
+ titleTextField.clearTextField()
+ titleTextField.setText(newTitle)
+ okButton.click()
+ // the dialog is not always dismissed on first click of the Ok button (not manually reproducible)
+ if (!mDevice.findObject(UiSelector().text("Rename")).waitUntilGone(waitingTimeShort)) {
+ okButton.click()
+ }
+ }
+
+ fun verifyFirstOnboardingScreenItems() {
+ assertTrue(onboardingCloseButton.waitForExists(waitingTime))
+ assertTrue(onboardingLogo.waitForExists(waitingTime))
+ assertTrue(onboardingFirstScreenTitle.waitForExists(waitingTime))
+ assertTrue(onboardingFirstScreenSubtitle.waitForExists(waitingTime))
+ assertTrue(onboardingGetStartedButton.waitForExists(waitingTime))
+ }
+
+ fun verifySecondOnboardingScreenItems() {
+ assertTrue(onboardingCloseButton.waitForExists(waitingTime))
+ assertTrue(onboardingLogo.waitForExists(waitingTime))
+ assertTrue(onboardingSecondScreenTitle.waitForExists(waitingTime))
+ assertTrue(onboardingSecondScreenFirstSubtitle.waitForExists(waitingTime))
+ assertTrue(onboardingSecondScreenSecondSubtitle.waitForExists(waitingTime))
+ assertTrue(onboardingSetAsDefaultBrowserButton.waitForExists(waitingTime))
+ assertTrue(onboardingSkipButton.waitForExists(waitingTime))
+ }
+
+ fun clickGetStartedButton() {
+ onboardingGetStartedButton
+ .also { it.waitForExists(waitingTime) }
+ .also { it.clickAndWaitForNewWindow(waitingTime) }
+ }
+
+ class Transition {
+ fun openMainMenu(interact: ThreeDotMainMenuRobot.() -> Unit): ThreeDotMainMenuRobot.Transition {
+ editURLBar.waitForExists(waitingTime)
+ mainMenu
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ ThreeDotMainMenuRobot().interact()
+ return ThreeDotMainMenuRobot.Transition()
+ }
+
+ fun clickPageShortcut(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ mDevice.findObject(UiSelector().text(title)).waitForExists(waitingTimeShort)
+ mDevice.findObject(UiSelector().text(title)).click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openSearchBar(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
+ editURLBar.waitForExists(waitingTime)
+ editURLBar.click()
+
+ SearchRobot().interact()
+ return SearchRobot.Transition()
+ }
+ }
+}
+
+fun homeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
+ HomeScreenRobot().interact()
+ return HomeScreenRobot.Transition()
+}
+
+private val editURLBar =
+ mDevice.findObject(
+ UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"),
+ )
+
+private val mainMenu = onView(withId(R.id.menuView))
+
+/********* First Run Locators */
+private val firstSlideTitle =
+ mDevice.findObject(UiSelector().textContains(getStringResource(R.string.firstrun_defaultbrowser_title)))
+
+private val secondSlideTitle =
+ mDevice.findObject(UiSelector().textContains(getStringResource(R.string.firstrun_search_title)))
+
+private val thirdSlideTitle =
+ mDevice.findObject(UiSelector().textContains(getStringResource(R.string.firstrun_shortcut_title)))
+
+private val lastSlide =
+ mDevice.findObject(UiSelector().textContains(getStringResource(R.string.firstrun_privacy_title)))
+
+private val nextButton = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/next")
+ .enabled(true),
+)
+
+private val finishButton = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/finish")
+ .enabled(true),
+)
+
+private val topSitesList = mDevice.findObject(UiSelector().resourceId("$packageName:id/topSites"))
+
+/** New onboarding elements **/
+
+private val onboardingCloseButton =
+ mDevice.findObject(
+ UiSelector()
+ .descriptionContains(getStringResource(R.string.onboarding_close_button_content_description)),
+ )
+
+private val onboardingLogo =
+ mDevice.findObject(
+ UiSelector()
+ .className("android.widget.ImageView")
+ .descriptionContains(appName),
+ )
+
+private val onboardingFirstScreenTitle =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_first_screen_title)),
+ )
+
+private val onboardingSecondScreenTitle =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_short_app_name) + " isn’t like other browsers"),
+ )
+
+private val onboardingFirstScreenSubtitle =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_first_screen_subtitle)),
+ )
+
+private val onboardingSecondScreenFirstSubtitle =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_second_screen_subtitle_one)),
+ )
+
+private val onboardingSecondScreenSecondSubtitle =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(
+ "Make " + getStringResource(R.string.onboarding_short_app_name) +
+ " your default to protect your data with every link you open.",
+ ),
+ )
+
+private val onboardingGetStartedButton =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_first_screen_button_text)),
+ )
+
+private val onboardingSetAsDefaultBrowserButton =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_second_screen_default_browser_button_text)),
+ )
+
+private val onboardingSkipButton =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.onboarding_second_screen_skip_button_text)),
+ )
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/NotificationRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/NotificationRobot.kt
new file mode 100644
index 0000000000..ce53788fb4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/NotificationRobot.kt
@@ -0,0 +1,159 @@
+/* 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.focus.activity.robots
+
+import android.os.Build
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.appName
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class NotificationRobot {
+
+ private val NOTIFICATION_SHADE = "com.android.systemui:id/notification_stack_scroller"
+ private val QS_PANEL = "com.android.systemui:id/quick_qs_panel"
+
+ fun clearNotifications() {
+ if (clearButton.exists()) {
+ clearButton.click()
+ } else {
+ notificationTray.flingToEnd(3)
+ if (clearButton.exists()) {
+ clearButton.click()
+ } else if (notificationTray.exists()) {
+ mDevice.pressBack()
+ }
+ }
+ }
+
+ fun expandEraseBrowsingNotification() {
+ notificationHeader.click()
+ }
+
+ fun verifySystemNotificationExists(notificationMessage: String) {
+ val notification = mDevice.findObject(UiSelector().text(notificationMessage))
+ while (!notification.waitForExists(waitingTime)) {
+ UiScrollable(
+ UiSelector().resourceId(NOTIFICATION_SHADE),
+ ).flingToEnd(1)
+ }
+
+ assertTrue(notification.exists())
+ }
+
+ fun verifyMediaNotificationExists(notificationMessage: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val notificationInTray = mDevice.wait(
+ Until.hasObject(
+ By.res(QS_PANEL).hasDescendant(
+ By.text(notificationMessage),
+ ),
+ ),
+ waitingTime,
+ )
+
+ assertTrue(notificationInTray)
+ } else {
+ verifySystemNotificationExists(notificationMessage)
+ }
+ }
+
+ fun verifyNotificationGone(notificationMessage: String) {
+ assertTrue(
+ mDevice.findObject(UiSelector().text(notificationMessage))
+ .waitUntilGone(waitingTime),
+ )
+ }
+
+ fun clickMediaNotificationControlButton(action: String) {
+ mediaNotificationControlButton(action).click()
+ }
+
+ fun verifyMediaNotificationButtonState(action: String) {
+ mediaNotificationControlButton(action).waitForExists(waitingTime)
+ }
+
+ fun verifyDownloadNotification(notificationMessage: String, fileName: String) {
+ val notification = UiSelector().text(notificationMessage)
+ var notificationFound = mDevice.findObject(notification).waitForExists(waitingTime)
+ val downloadFilename = mDevice.findObject(UiSelector().text(fileName))
+
+ while (!notificationFound) {
+ notificationTray.swipeUp(2)
+ notificationFound = mDevice.findObject(notification).waitForExists(waitingTime)
+ }
+
+ assertTrue(notificationFound)
+ assertTrue(downloadFilename.exists())
+ }
+
+ class Transition {
+ fun clickEraseAndOpenNotificationButton(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
+ notificationEraseAndOpenButton.waitForExists(waitingTime)
+ notificationEraseAndOpenButton.click()
+
+ HomeScreenRobot().interact()
+ return HomeScreenRobot.Transition()
+ }
+
+ fun clickNotificationOpenButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ notificationOpenButton.waitForExists(waitingTime)
+ notificationOpenButton.clickAndWaitForNewWindow()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun clickNotificationMessage(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
+ eraseBrowsingNotification.waitForExists(waitingTime)
+ eraseBrowsingNotification.click()
+
+ HomeScreenRobot().interact()
+ return HomeScreenRobot.Transition()
+ }
+ }
+}
+
+fun notificationTray(interact: NotificationRobot.() -> Unit): NotificationRobot.Transition {
+ NotificationRobot().interact()
+ return NotificationRobot.Transition()
+}
+
+private val eraseBrowsingNotification =
+ mDevice.findObject(
+ UiSelector().text(getStringResource(R.string.notification_erase_text)),
+ )
+
+private val notificationEraseAndOpenButton =
+ mDevice.findObject(
+ UiSelector().description(getStringResource(R.string.notification_action_erase_and_open)),
+ )
+
+private val notificationOpenButton = mDevice.findObject(
+ UiSelector().description(getStringResource(R.string.notification_action_open)),
+)
+
+private val notificationTray = UiScrollable(
+ UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"),
+)
+ .setAsVerticalList()
+
+private val notificationHeader = mDevice.findObject(
+ UiSelector()
+ .resourceId("android:id/app_name_text")
+ .textContains(appName),
+)
+
+private val clearButton =
+ mDevice.findObject(UiSelector().resourceId("com.android.systemui:id/btn_clear_all"))
+
+private fun mediaNotificationControlButton(action: String) =
+ mDevice.findObject(UiSelector().description(action))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SearchRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SearchRobot.kt
new file mode 100644
index 0000000000..490f4cb9ab
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SearchRobot.kt
@@ -0,0 +1,166 @@
+/* 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.focus.activity.robots
+
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiSelector
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.Constants.LONG_CLICK_DURATION
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.pressEnterKey
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.idlingResources.SessionLoadedIdlingResource
+
+class SearchRobot {
+
+ fun verifySearchBarIsDisplayed() = assertTrue(searchBar.exists())
+
+ fun typeInSearchBar(searchString: String) {
+ assertTrue(searchBar.waitForExists(waitingTime))
+ searchBar.clearTextField()
+ searchBar.setText(searchString)
+ }
+
+ // Would you like to turn on search suggestions? Yes No
+ // fresh install only
+ fun allowEnableSearchSuggestions() {
+ if (searchSuggestionsTitle.waitForExists(waitingTime)) {
+ searchSuggestionsButtonYes.waitForExists(waitingTime)
+ searchSuggestionsButtonYes.click()
+ }
+ }
+
+ // Would you like to turn on search suggestions? Yes No
+ // fresh install only
+ fun denyEnableSearchSuggestions() {
+ if (searchSuggestionsTitle.waitForExists(waitingTime)) {
+ searchSuggestionsButtonNo.waitForExists(waitingTime)
+ searchSuggestionsButtonNo.click()
+ }
+ }
+
+ fun verifySearchSuggestionsAreShown() {
+ suggestionsList.waitForExists(waitingTime)
+ assertTrue(suggestionsList.childCount >= 1)
+ }
+
+ fun verifySearchSuggestionsAreNotShown() {
+ assertFalse(suggestionsList.exists())
+ }
+
+ fun verifySearchEditBarContainsText(text: String) {
+ mDevice.findObject(UiSelector().textContains(text)).waitForExists(waitingTime)
+ assertTrue(searchBar.text.equals(text))
+ }
+
+ fun verifySearchEditBarIsEmpty() {
+ searchBar.waitForExists(waitingTime)
+ assertTrue(searchBar.text.equals(getStringResource(R.string.urlbar_hint)))
+ }
+
+ fun clickToolbar() {
+ toolbar.waitForExists(waitingTime)
+ toolbar.click()
+ }
+
+ fun longPressSearchBar() {
+ searchBar.waitForExists(waitingTime)
+ mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_edit_url_view")).click(LONG_CLICK_DURATION)
+ }
+
+ fun clearSearchBar() = clearSearchButton.click()
+
+ fun verifySearchSuggestionsContain(title: String) {
+ assertTrue(
+ suggestionsList.getChild(UiSelector().textContains(title)).waitForExists(waitingTime),
+ )
+ }
+
+ class Transition {
+
+ private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
+
+ fun loadPage(url: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ val geckoEngineView = mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
+ val trackingProtectionDialog = mDevice.findObject(UiSelector().resourceId("$packageName:id/message"))
+
+ sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+
+ searchScreen { typeInSearchBar(url) }
+ pressEnterKey()
+
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ assertTrue(
+ BrowserRobot().progressBar.waitUntilGone(waitingTime),
+ )
+ assertTrue(
+ geckoEngineView.waitForExists(waitingTime) ||
+ trackingProtectionDialog.waitForExists(waitingTime),
+ )
+ }
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun pasteAndLoadLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ var currentTries = 0
+ while (currentTries++ < 3) {
+ try {
+ mDevice.findObject(UiSelector().textContains("Paste")).waitForExists(waitingTime)
+ val pasteText = mDevice.findObject(By.textContains("Paste"))
+ pasteText.click()
+ mDevice.pressEnter()
+ break
+ } catch (e: NullPointerException) {
+ SearchRobot().longPressSearchBar()
+ }
+ }
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+fun searchScreen(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
+ SearchRobot().interact()
+ return SearchRobot.Transition()
+}
+
+private val searchBar =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"))
+
+private val toolbar =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view"))
+
+private val searchSuggestionsTitle = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/enable_search_suggestions_title")
+ .enabled(true),
+)
+
+private val searchSuggestionsButtonYes = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/enable_search_suggestions_button")
+ .enabled(true),
+)
+
+private val searchSuggestionsButtonNo = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/disable_search_suggestions_button")
+ .enabled(true),
+)
+
+private val suggestionsList = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/search_suggestions_view"),
+)
+
+private val clearSearchButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_clear_view"))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsAdvancedMenuRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsAdvancedMenuRobot.kt
new file mode 100644
index 0000000000..7c44b0a015
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsAdvancedMenuRobot.kt
@@ -0,0 +1,109 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isChecked
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import org.hamcrest.CoreMatchers.allOf
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.EspressoHelper.hasCousin
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class SettingsAdvancedMenuRobot {
+ fun verifyAdvancedSettingsItems() {
+ advancedSettingsList.waitForExists(waitingTime)
+ developerToolsHeading().check(matches(isDisplayed()))
+ remoteDebuggingSwitch().check(matches(isDisplayed()))
+ assertRemoteDebuggingSwitchState()
+ openLinksInAppsButton().check(matches(isDisplayed()))
+ assertOpenLinksInAppsSwitchState()
+ }
+
+ fun verifyOpenLinksInAppsSwitchState(enabled: Boolean) = assertOpenLinksInAppsSwitchState(enabled)
+ fun clickOpenLinksInAppsSwitch() = openLinksInAppsButton().perform(click())
+
+ class Transition {
+ fun goBackToSettings(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
+ mDevice.pressBack()
+ SettingsRobot().interact()
+ return SettingsRobot.Transition()
+ }
+ }
+}
+
+private fun openLinksInAppsButton() = onView(withText(R.string.preferences_open_links_in_apps))
+
+private fun assertOpenLinksInAppsSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ openLinksInAppsButton()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ openLinksInAppsButton()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private val advancedSettingsList =
+ UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
+
+private fun developerToolsHeading() = onView(withText(R.string.preference_advanced_summary))
+
+private fun remoteDebuggingSwitch() = onView(withText("Remote debugging via USB/Wi-Fi"))
+
+private fun assertRemoteDebuggingSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ remoteDebuggingSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ remoteDebuggingSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsGeneralMenuRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsGeneralMenuRobot.kt
new file mode 100644
index 0000000000..4601e7d6bb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsGeneralMenuRobot.kt
@@ -0,0 +1,197 @@
+/* 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.focus.activity.robots
+
+import android.os.Build
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.ViewInteraction
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.matcher.IntentMatchers
+import androidx.test.espresso.matcher.ViewMatchers.hasSibling
+import androidx.test.espresso.matcher.ViewMatchers.isChecked
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import junit.framework.TestCase.assertTrue
+import org.hamcrest.Matchers.allOf
+import org.junit.Assert.assertFalse
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.EspressoHelper.hasCousin
+import org.mozilla.focus.helpers.TestHelper.appName
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class SettingsGeneralMenuRobot {
+ companion object {
+ const val ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE"
+ }
+
+ fun verifyGeneralSettingsItems(defaultBrowserSwitchState: Boolean = false) {
+ verifyThemesList()
+ languageMenuButton().check(matches(isDisplayed()))
+ defaultBrowserSwitch().check(matches(isDisplayed()))
+ assertDefaultBrowserSwitchState(defaultBrowserSwitchState)
+ }
+
+ fun clickSetDefaultBrowser() {
+ defaultBrowserSwitch()
+ .check(matches(isDisplayed()))
+ .perform(click())
+ }
+
+ fun verifyAndroidDefaultAppsMenuAppears() {
+ // method used to assert the default apps menu on API 24 and above
+ when (Build.VERSION.SDK_INT) {
+ in Build.VERSION_CODES.N..Build.VERSION_CODES.P ->
+ assertTrue(
+ mDevice.findObject(UiSelector().resourceId("com.android.settings:id/list"))
+ .waitForExists(waitingTime),
+ )
+ in Build.VERSION_CODES.Q..Build.VERSION_CODES.R ->
+ intended(IntentMatchers.hasAction(ACTION_REQUEST_ROLE))
+ }
+ }
+
+ fun selectFocusDefaultBrowser() {
+ // method used to set default browser on API 30 and above
+ mDevice.findObject(UiSelector().text(appName)).click()
+ mDevice.findObject(UiSelector().textContains("Set as default")).click()
+ }
+
+ fun verifySwitchIsToggled(checked: Boolean) {
+ onView(withId(R.id.switch_widget)).check(
+ matches(
+ if (checked) {
+ isChecked()
+ } else {
+ isNotChecked()
+ },
+ ),
+ )
+ }
+
+ fun openLanguageSelectionMenu(localizedText: String = "Language"): ViewInteraction =
+ languageMenuButton(localizedText).perform(click())
+
+ fun verifySystemLocaleSelected(localizedText: String = "System default") {
+ assertTrue(
+ languageMenu.getChild(
+ UiSelector()
+ .text(localizedText)
+ .index(1),
+ ).getFromParent(
+ UiSelector().index(0),
+ ).isChecked,
+ )
+ }
+
+ fun selectLanguage(language: String) {
+ // Due to scrolling issues on Compose LazyList, avoid setting a language that is under the fold.
+ // see https://github.com/mozilla-mobile/focus-android/issues/7282
+ languageMenu
+ .getChildByText(UiSelector().text(language), language, true)
+ .click()
+ }
+
+ fun verifyThemesList() {
+ darkThemeToggle.check(matches(isDisplayed()))
+ lightThemeToggle.check(matches(isDisplayed()))
+ deviceThemeToggle
+ .check(matches(isDisplayed()))
+ .check(matches(isChecked()))
+ }
+
+ fun verifyThemeApplied(isDarkTheme: Boolean = false, isLightTheme: Boolean = false, getThemeState: Boolean) {
+ when {
+ // getUiTheme() returns true if dark is applied
+ isDarkTheme -> assertTrue("Dark theme not applied", getThemeState)
+ // getUiTheme() returns false if light is applied
+ isLightTheme -> assertFalse("Light theme not applied", getThemeState)
+ }
+ }
+
+ fun selectDarkTheme() = darkThemeToggle.perform(click())
+
+ fun selectLightTheme() = lightThemeToggle.perform(click())
+
+ fun selectDeviceTheme() = deviceThemeToggle.perform(click())
+
+ class Transition {
+ // add here transitions to other robot classes
+ }
+}
+
+private fun defaultBrowserSwitch() = onView(withText("Make $appName default browser"))
+
+private fun assertDefaultBrowserSwitchState(enabled: Boolean) {
+ if (enabled) {
+ defaultBrowserSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switch_widget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ defaultBrowserSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switch_widget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private val openWithDialogTitle = mDevice.findObject(
+ UiSelector()
+ .text("Open with"),
+)
+
+private val openWithList = mDevice.findObject(
+ UiSelector()
+ .resourceId("android:id/resolver_list"),
+)
+
+private fun languageMenuButton(localizedText: String = "Language") = onView(withText(localizedText))
+
+private val languageMenu = UiScrollable(UiSelector().scrollable(true))
+
+private val darkThemeToggle =
+ onView(
+ allOf(
+ withId(R.id.radio_button),
+ hasSibling(withText("Dark")),
+ ),
+ )
+
+private val lightThemeToggle =
+ onView(
+ allOf(
+ withId(R.id.radio_button),
+ hasSibling(withText("Light")),
+ ),
+ )
+
+private val deviceThemeToggle =
+ onView(
+ allOf(
+ withId(R.id.radio_button),
+ hasSibling(withText("Follow device theme")),
+ ),
+ )
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsMozillaMenuRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsMozillaMenuRobot.kt
new file mode 100644
index 0000000000..c5ce1748eb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsMozillaMenuRobot.kt
@@ -0,0 +1,187 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.hasSibling
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withParent
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import junit.framework.TestCase.assertTrue
+import mozilla.components.support.utils.ext.getPackageInfoCompat
+import org.hamcrest.Matchers.allOf
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.appName
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.idlingResources.SessionLoadedIdlingResource
+
+class SettingsMozillaMenuRobot {
+
+ private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
+
+ fun verifyMozillaMenuItems() {
+ mozillaSettingsList.waitForExists(waitingTime)
+ aboutFocusPageLink.check(matches(isDisplayed()))
+ helpPageLink.check(matches(isDisplayed()))
+ yourRightsLink.check(matches(isDisplayed()))
+ privacyNoticeLink.check(matches(isDisplayed()))
+ licenseInfo.check(matches(isDisplayed()))
+ librariesUsedLink.check(matches(isDisplayed()))
+ }
+
+ fun verifyVersionNumbers() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
+ val versionName = packageInfo.versionName
+ val gvBuildId = org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
+ val gvVersion = org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION
+
+ sessionLoadedIdlingResource = SessionLoadedIdlingResource()
+
+ runWithIdleRes(sessionLoadedIdlingResource) {
+ assertTrue(
+ "Expected app version number not found",
+ mDevice.findObject(UiSelector().textContains(versionName))
+ .waitForExists(waitingTime),
+ )
+
+ assertTrue(
+ "Expected GV version not found",
+ mDevice.findObject(UiSelector().textContains(gvVersion))
+ .waitForExists(waitingTime),
+ )
+
+ assertTrue(
+ "Expected GV build ID not found",
+ mDevice.findObject(UiSelector().textContains(gvBuildId))
+ .waitForExists(waitingTime),
+ )
+ }
+ }
+
+ fun verifyLibrariesUsedTitle() {
+ librariesUsedTitle
+ .check(matches(isDisplayed()))
+ }
+
+ class Transition {
+ fun openAboutPage(interact: SettingsMozillaMenuRobot.() -> Unit): Transition {
+ aboutFocusPageLink
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ SettingsMozillaMenuRobot().interact()
+ return Transition()
+ }
+
+ fun openAboutPageLearnMoreLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ mDevice.findObject(UiSelector().text("Learn more")).click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openYourRightsPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ yourRightsLink
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openLicenseInformation(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ licenseInfo
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openLibrariesUsedPage(interact: SettingsMozillaMenuRobot.() -> Unit): BrowserRobot.Transition {
+ librariesUsedLink
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ SettingsMozillaMenuRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openPrivacyNotice(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ privacyNoticeLink
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openHelpLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ helpPageLink
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+private val mozillaSettingsList =
+ UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
+
+private val aboutFocusPageLink = onView(withText("About $appName"))
+
+private val helpPageLink =
+ onView(
+ allOf(
+ withText("Help"),
+ withParent(
+ hasSibling(withId(R.id.icon_frame)),
+ ),
+ ),
+ )
+
+private val yourRightsLink =
+ onView(
+ allOf(
+ withText("Your Rights"),
+ withParent(
+ hasSibling(withId(R.id.icon_frame)),
+ ),
+ ),
+ )
+
+private val privacyNoticeLink =
+ onView(
+ allOf(
+ withText("Privacy Notice"),
+ withParent(
+ hasSibling(withId(R.id.icon_frame)),
+ ),
+ ),
+ )
+
+private val licenseInfo =
+ onView(
+ allOf(
+ withText("Licensing information"),
+ withParent(
+ hasSibling(withId(R.id.icon_frame)),
+ ),
+ ),
+ )
+
+private val librariesUsedLink = onView(withText("Libraries that we use"))
+private val librariesUsedTitle = onView(withText("$appName | OSS Libraries"))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsPrivacyMenuRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsPrivacyMenuRobot.kt
new file mode 100644
index 0000000000..7c549c4d36
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsPrivacyMenuRobot.kt
@@ -0,0 +1,741 @@
+/* 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/. */
+
+@file:Suppress("TooManyFunctions")
+
+package org.mozilla.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
+import androidx.test.espresso.ViewInteraction
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isChecked
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isEnabled
+import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import org.hamcrest.CoreMatchers.allOf
+import org.hamcrest.CoreMatchers.containsString
+import org.hamcrest.Matchers
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.EspressoHelper.hasCousin
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+
+class SettingsPrivacyMenuRobot {
+
+ fun verifyPrivacySettingsItems() {
+ privacySettingsList.waitForExists(waitingTime)
+ adTrackersBlockSwitch().check(matches(isDisplayed()))
+ assertAdTrackersBlockSwitchState()
+ analyticTrackersBlockSwitch().check(matches(isDisplayed()))
+ assertAnalyticTrackersBlockSwitchState()
+ socialTrackersBlockSwitch().check(matches(isDisplayed()))
+ assertSocialTrackersBlockSwitchState()
+ otherContentTrackersBlockSwitch().check(matches(isDisplayed()))
+ assertOtherContentTrackersBlockSwitchState()
+ blockWebFontsSwitch().check(matches(isDisplayed()))
+ assertBlockWebFontsSwitchState()
+ blockJavaScriptSwitch().check(matches(isDisplayed()))
+ assertBlockJavaScriptSwitchState()
+ assertTrue(cookiesAndSiteDataSection().exists())
+ assertTrue(blockCookiesMenuButton().exists())
+ assertTrue(blockCookiesDefaultOption().exists())
+ assertTrue(sitePermissions().exists())
+ verifyExceptionsListDisabled()
+ useFingerprintSwitch().check(matches(isDisplayed()))
+ assertUseFingerprintSwitchState()
+ stealthModeSwitch().check(matches(isDisplayed()))
+ assertStealthModeSwitchState()
+ safeBrowsingSwitch().check(matches(isDisplayed()))
+ assertSafeBrowsingSwitchState()
+ httpsOnlyModeSwitch().check(matches(isDisplayed()))
+ assertHttpsOnlyModeSwitchState()
+ sendDataSwitch().check(matches(isDisplayed()))
+ if (packageName != "org.mozilla.focus.debug") {
+ assertSendDataSwitchState(true)
+ } else {
+ assertSendDataSwitchState()
+ }
+ studiesOption().check(matches(isDisplayed()))
+ studiesDefaultOption().check(matches(isDisplayed()))
+ }
+
+ fun verifyCookiesAndSiteDataSection() {
+ privacySettingsList.waitForExists(waitingTime)
+ assertTrue(cookiesAndSiteDataSection().exists())
+ assertTrue(blockCookiesMenuButton().exists())
+ assertTrue(blockCookiesDefaultOption().exists())
+ assertTrue(sitePermissions().exists())
+ }
+
+ fun verifyBlockCookiesPrompt() {
+ assertTrue(blockCookiesPromptHeading.waitForExists(waitingTimeShort))
+ assertTrue(blockCookiesYesPleaseOption.waitForExists(waitingTimeShort))
+ assertTrue(block3rdPartyCookiesOnlyOption.waitForExists(waitingTimeShort))
+ assertTrue(block3rdPartyTrackerCookiesOnlyOption.waitForExists(waitingTimeShort))
+ assertTrue(blockCrossSiteCookiesOption.waitForExists(waitingTimeShort))
+ assertTrue(noThanksOption.waitForExists(waitingTimeShort))
+ assertTrue(cancelBlockCookiesPrompt.waitForExists(waitingTimeShort))
+ }
+
+ fun verifyBlockAdTrackersEnabled(enabled: Boolean) {
+ if (enabled) {
+ adTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ adTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+ }
+
+ fun verifyBlockAnalyticTrackersEnabled(enabled: Boolean) {
+ if (enabled) {
+ analyticTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ analyticTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+ }
+
+ fun verifyBlockSocialTrackersEnabled(enabled: Boolean) {
+ if (enabled) {
+ socialTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ socialTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+ }
+
+ fun verifyBlockOtherTrackersEnabled(shouldBeEnabled: Boolean) {
+ if (shouldBeEnabled) {
+ otherContentTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ otherContentTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+ }
+
+ fun clickAdTrackersBlockSwitch() = adTrackersBlockSwitch().perform(click())
+
+ fun clickAnalyticsTrackersBlockSwitch() = analyticTrackersBlockSwitch().perform(click())
+
+ fun clickSocialTrackersBlockSwitch() = socialTrackersBlockSwitch().perform(click())
+
+ fun clickOtherContentTrackersBlockSwitch() = otherContentTrackersBlockSwitch().perform(click())
+
+ fun clickBlockCookies() = blockCookiesMenuButton().click()
+
+ fun clickCancelBlockCookiesPrompt() {
+ cancelBlockCookiesPrompt.click()
+ mDevice.waitForIdle(waitingTimeShort)
+ }
+
+ fun clickYesPleaseOption() = blockCookiesYesPleaseOption.click()
+ fun clickBlockThirdPartyCookiesOnly() = block3rdPartyCookiesOnlyOption.click()
+
+ fun switchSafeBrowsingToggle(): ViewInteraction = safeBrowsingSwitch().perform(click())
+
+ fun verifyExceptionsListDisabled() {
+ exceptionsList()
+ .check(matches(Matchers.not(isEnabled())))
+ }
+
+ fun openExceptionsList() {
+ exceptionsList()
+ .check(matches(isEnabled()))
+ .perform(click())
+ }
+
+ fun verifyExceptionURL(url: String) {
+ onView(withId(R.id.domainView)).check(matches(withText(containsString(url))))
+ }
+
+ fun removeException() {
+ openActionBarOverflowOrOptionsMenu(getTargetContext)
+ onView(withText("Remove"))
+ .perform(click())
+ onView(withId(R.id.checkbox))
+ .perform(click())
+ onView(withId(R.id.remove))
+ .perform(click())
+ }
+
+ fun removeAllExceptions() {
+ onView(withId(R.id.removeAllExceptions))
+ .perform(click())
+ }
+
+ class Transition {
+ fun goBackToSettings(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
+ mDevice.pressBack()
+
+ SettingsRobot().interact()
+ return SettingsRobot.Transition()
+ }
+
+ fun clickSitePermissionsSettings(interact: SettingsSitePermissionsRobot.() -> Unit): SettingsSitePermissionsRobot.Transition {
+ sitePermissions().waitForExists(waitingTime)
+ sitePermissions().click()
+
+ SettingsSitePermissionsRobot().interact()
+ return SettingsSitePermissionsRobot.Transition()
+ }
+ }
+}
+
+private val privacySettingsList =
+ UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
+
+private fun adTrackersBlockSwitch(): ViewInteraction {
+ privacySettingsList
+ .scrollTextIntoView("Block ad trackers")
+ return onView(withText("Block ad trackers"))
+}
+
+private fun assertAdTrackersBlockSwitchState(enabled: Boolean = true) {
+ if (enabled) {
+ adTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ adTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun analyticTrackersBlockSwitch(): ViewInteraction {
+ privacySettingsList
+ .scrollTextIntoView("Block analytic trackers")
+ return onView(withText("Block analytic trackers"))
+}
+
+private fun assertAnalyticTrackersBlockSwitchState(enabled: Boolean = true) {
+ if (enabled) {
+ analyticTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ analyticTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun socialTrackersBlockSwitch(): ViewInteraction {
+ privacySettingsList
+ .scrollTextIntoView("Block social trackers")
+ return onView(withText("Block social trackers"))
+}
+
+private fun assertSocialTrackersBlockSwitchState(enabled: Boolean = true) {
+ if (enabled) {
+ socialTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ socialTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun otherContentTrackersBlockSwitch(): ViewInteraction {
+ privacySettingsList
+ .scrollTextIntoView("Block other content trackers")
+ return onView(withText("Block other content trackers"))
+}
+
+private fun assertOtherContentTrackersBlockSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ otherContentTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ otherContentTrackersBlockSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun blockWebFontsSwitch(): ViewInteraction {
+ privacySettingsList
+ .scrollTextIntoView("Block web fonts")
+ return onView(withText("Block web fonts"))
+}
+
+private fun assertBlockWebFontsSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ blockWebFontsSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ blockWebFontsSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun blockJavaScriptSwitch(): ViewInteraction {
+ privacySettingsList
+ .scrollTextIntoView("Block JavaScript")
+ return onView(withText("Block JavaScript"))
+}
+
+private fun assertBlockJavaScriptSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ blockJavaScriptSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ blockJavaScriptSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun cookiesAndSiteDataSection() =
+ privacySettingsList
+ .getChildByText(
+ UiSelector().text("Cookies and Site Data"),
+ "Cookies and Site Data",
+ true,
+ )
+
+private fun blockCookiesMenuButton() =
+ privacySettingsList
+ .getChildByText(
+ UiSelector().text("Block cookies"),
+ "Block cookies",
+ true,
+ )
+
+private fun blockCookiesDefaultOption() =
+ privacySettingsList
+ .getChildByText(
+ UiSelector().text("Block cross-site cookies"),
+ "Block cross-site cookies",
+ true,
+ )
+
+private fun sitePermissions() =
+ privacySettingsList
+ .getChildByText(UiSelector().text("Site permissions"), "Site permissions", true)
+
+private fun useFingerprintSwitch(): ViewInteraction {
+ val useFingerprintSwitchSummary = getStringResource(R.string.preference_security_biometric_summary2)
+ privacySettingsList.scrollTextIntoView(useFingerprintSwitchSummary)
+ return onView(withText(useFingerprintSwitchSummary))
+}
+
+private fun assertUseFingerprintSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ useFingerprintSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ useFingerprintSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun stealthModeSwitch(): ViewInteraction {
+ val stealthModeSwitchSummary = getStringResource(R.string.preference_privacy_stealth_summary)
+ privacySettingsList.scrollTextIntoView(stealthModeSwitchSummary)
+ return onView(withText(stealthModeSwitchSummary))
+}
+
+private fun assertStealthModeSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ stealthModeSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ stealthModeSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun safeBrowsingSwitch(): ViewInteraction {
+ val safeBrowsingSwitchText =
+ mDevice.findObject(
+ UiSelector().text(
+ getStringResource(R.string.preference_safe_browsing_summary),
+ ),
+ )
+ privacySettingsList.scrollToEnd(3)
+ privacySettingsList.scrollIntoView(safeBrowsingSwitchText)
+ return onView(withText(getStringResource(R.string.preference_safe_browsing_summary)))
+}
+
+private fun assertSafeBrowsingSwitchState(enabled: Boolean = true) {
+ if (enabled) {
+ safeBrowsingSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ safeBrowsingSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun httpsOnlyModeSwitch(): ViewInteraction {
+ val httpsOnlyModeSwitchText = getStringResource(R.string.preference_https_only_title)
+ privacySettingsList.scrollTextIntoView(httpsOnlyModeSwitchText)
+ return onView(withText(httpsOnlyModeSwitchText))
+}
+
+private fun assertHttpsOnlyModeSwitchState(enabled: Boolean = true) {
+ if (enabled) {
+ httpsOnlyModeSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ httpsOnlyModeSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun sendDataSwitch(): ViewInteraction {
+ val sendDataSwitchSummary = getStringResource(R.string.preference_mozilla_telemetry_summary2)
+ privacySettingsList.scrollTextIntoView(sendDataSwitchSummary)
+ return onView(withText(sendDataSwitchSummary))
+}
+
+private fun assertSendDataSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ sendDataSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isChecked(),
+ ),
+ ),
+ ),
+ )
+ } else {
+ sendDataSwitch()
+ .check(
+ matches(
+ hasCousin(
+ allOf(
+ withId(R.id.switchWidget),
+ isNotChecked(),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+private fun studiesOption(): ViewInteraction {
+ val studies = getStringResource(R.string.preference_studies)
+ privacySettingsList.scrollTextIntoView(studies)
+ return onView(withText(R.string.preference_studies))
+}
+
+private fun studiesDefaultOption(): ViewInteraction {
+ privacySettingsList.scrollToEnd(3)
+ return onView(withText(R.string.preference_state_on))
+}
+
+private fun exceptionsList(): ViewInteraction {
+ val exceptionsTitle = getStringResource(R.string.preference_exceptions)
+ privacySettingsList.scrollTextIntoView(exceptionsTitle)
+ return onView(withText(exceptionsTitle))
+}
+
+private val blockCookiesPromptHeading =
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/alertTitle")
+ .textContains(getStringResource(R.string.preference_block_cookies_title)),
+ )
+
+private val blockCookiesYesPleaseOption =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_privacy_should_block_cookies_yes_option2)),
+ )
+
+private val block3rdPartyCookiesOnlyOption =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_privacy_should_block_cookies_third_party_only_option)),
+ )
+
+private val block3rdPartyTrackerCookiesOnlyOption =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_privacy_should_block_cookies_third_party_tracker_cookies_option)),
+ )
+
+private val blockCrossSiteCookiesOption =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_privacy_should_block_cookies_cross_site_option)),
+ )
+
+private val noThanksOption =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_privacy_should_block_cookies_no_option2)),
+ )
+
+private val cancelBlockCookiesPrompt =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.action_cancel)),
+ )
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsRobot.kt
new file mode 100644
index 0000000000..b29032d656
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsRobot.kt
@@ -0,0 +1,114 @@
+/* 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.focus.activity.robots
+
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class SettingsRobot {
+
+ fun verifySettingsMenuItems() {
+ settingsMenuList.waitForExists(waitingTime)
+ assertTrue(generalSettingsMenu().exists())
+ assertTrue(searchSettingsMenu.exists())
+ assertTrue(privacySettingsMenu.exists())
+ assertTrue(advancedSettingsMenu.exists())
+ assertTrue(mozillaSettingsMenu.exists())
+ }
+
+ class Transition {
+ fun openSearchSettingsMenu(interact: SearchSettingsRobot.() -> Unit): SearchSettingsRobot.Transition {
+ searchSettingsMenu.waitForExists(waitingTime)
+ searchSettingsMenu.click()
+
+ SearchSettingsRobot().interact()
+ return SearchSettingsRobot.Transition()
+ }
+
+ fun openGeneralSettingsMenu(
+ localizedText: String = getStringResource(R.string.preference_category_general),
+ interact: SettingsGeneralMenuRobot.() -> Unit,
+ ): SettingsGeneralMenuRobot.Transition {
+ generalSettingsMenu(localizedText).waitForExists(waitingTime)
+ generalSettingsMenu(localizedText).click()
+
+ SettingsGeneralMenuRobot().interact()
+ return SettingsGeneralMenuRobot.Transition()
+ }
+
+ fun openPrivacySettingsMenu(
+ interact: SettingsPrivacyMenuRobot.() -> Unit,
+ ): SettingsPrivacyMenuRobot.Transition {
+ privacySettingsMenu.waitForExists(waitingTime)
+ privacySettingsMenu.click()
+
+ SettingsPrivacyMenuRobot().interact()
+ return SettingsPrivacyMenuRobot.Transition()
+ }
+
+ fun openAdvancedSettingsMenu(
+ interact: SettingsAdvancedMenuRobot.() -> Unit,
+ ): SettingsAdvancedMenuRobot.Transition {
+ advancedSettingsMenu.waitForExists(waitingTime)
+ advancedSettingsMenu.click()
+
+ SettingsAdvancedMenuRobot().interact()
+ return SettingsAdvancedMenuRobot.Transition()
+ }
+
+ fun openMozillaSettingsMenu(
+ interact: SettingsMozillaMenuRobot.() -> Unit,
+ ): SettingsMozillaMenuRobot.Transition {
+ mozillaSettingsMenu.waitForExists(waitingTime)
+ mozillaSettingsMenu.click()
+
+ SettingsMozillaMenuRobot().interact()
+ return SettingsMozillaMenuRobot.Transition()
+ }
+
+ fun goBackToHomeScreen(
+ interact: SearchRobot.() -> Unit,
+ ): SearchRobot.Transition {
+ mDevice.pressBack()
+
+ SearchRobot().interact()
+ return SearchRobot.Transition()
+ }
+ }
+}
+
+private val settingsMenuList =
+ UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
+
+private fun generalSettingsMenu(localizedText: String = getStringResource(R.string.preference_category_general)) =
+ settingsMenuList.getChild(
+ UiSelector()
+ .text(localizedText),
+ )
+
+private val searchSettingsMenu = settingsMenuList.getChild(
+ UiSelector()
+ .text(getStringResource(R.string.preference_category_search)),
+)
+
+private val privacySettingsMenu = settingsMenuList.getChild(
+ UiSelector()
+ .text(getStringResource(R.string.preference_privacy_and_security_header)),
+)
+
+private val advancedSettingsMenu = settingsMenuList.getChild(
+ UiSelector()
+ .text(getStringResource(R.string.preference_category_advanced)),
+)
+
+private val mozillaSettingsMenu = settingsMenuList.getChild(
+ UiSelector()
+ .text(getStringResource(R.string.preference_mozilla_summary)),
+)
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSearchMenuRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSearchMenuRobot.kt
new file mode 100644
index 0000000000..f96d01fbd0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSearchMenuRobot.kt
@@ -0,0 +1,169 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.typeText
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class SearchSettingsRobot {
+
+ fun verifySearchSettingsItems() {
+ assertTrue(searchEngineSubMenu.exists())
+ assertTrue(searchEngineDefaultOption.exists())
+ assertTrue(searchSuggestionsHeading.exists())
+ assertTrue(searchSuggestionDescription.exists())
+ verifySearchSuggestionsSwitchState()
+ assertTrue(searchSuggestionLearnMoreLink.exists())
+ assertTrue(urlAutocompleteSubMenu.exists())
+ assertTrue(urlAutocompleteDefaultOption.exists())
+ }
+
+ fun openSearchEngineSubMenu() {
+ searchEngineSubMenu.waitForExists(waitingTime)
+ searchEngineSubMenu.click()
+ }
+
+ fun selectSearchEngine(engineName: String) {
+ searchEngineList.waitForExists(waitingTime)
+ searchEngineList
+ .getChild(UiSelector().text(engineName))
+ .click()
+ }
+
+ fun clickSearchSuggestionsSwitch() {
+ searchSuggestionsSwitch.waitForExists(waitingTime)
+ searchSuggestionsSwitch.click()
+ }
+
+ fun verifySearchSuggestionsSwitchState(enabled: Boolean = false) {
+ if (enabled) {
+ assertTrue(searchSuggestionsSwitch.isChecked)
+ } else {
+ assertFalse(searchSuggestionsSwitch.isChecked)
+ }
+ }
+
+ fun openUrlAutocompleteSubMenu() {
+ urlAutocompleteSubMenu.waitForExists(waitingTime)
+ urlAutocompleteSubMenu.click()
+ }
+
+ fun openManageSitesSubMenu() {
+ manageSitesSubMenu.check(matches(isDisplayed()))
+ manageSitesSubMenu.perform(click())
+ }
+
+ fun openAddCustomUrlDialog() {
+ addCustomUrlButton.check(matches(isDisplayed()))
+ addCustomUrlButton.perform(click())
+ }
+
+ fun enterCustomUrl(url: String) {
+ customUrlText.check(matches(isDisplayed()))
+ customUrlText.perform(typeText(url))
+ }
+
+ fun saveCustomUrl() = saveButton.perform(click())
+
+ fun verifySavedCustomURL(url: String) {
+ customUrlText.check(matches(withText(url)))
+ }
+
+ fun removeCustomUrl() {
+ Espresso.openActionBarOverflowOrOptionsMenu(getTargetContext)
+ onView(withText(R.string.preference_autocomplete_menu_remove)).perform(click())
+ customUrlText.perform(click())
+ onView(withId(R.id.remove)).perform(click())
+ }
+
+ fun verifyCustomUrlDialogNotClosed() {
+ saveButton.check(matches(isDisplayed()))
+ }
+
+ fun toggleCustomAutocomplete() {
+ onView(withText(R.string.preference_switch_autocomplete_user_list)).perform(click())
+ }
+
+ fun toggleTopSitesAutocomplete() {
+ onView(withText(R.string.preference_switch_autocomplete_topsites)).perform(click())
+ }
+
+ class Transition
+}
+
+private val searchEngineSubMenu =
+ UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
+ .getChild(UiSelector().text(getStringResource(R.string.preference_search_engine_label)))
+
+private val searchEngineDefaultOption =
+ mDevice.findObject(UiSelector().textContains("Google"))
+
+private val searchEngineList = UiScrollable(
+ UiSelector()
+ .resourceId("$packageName:id/search_engine_group").enabled(true),
+)
+
+private val searchSuggestionsHeading: UiObject =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_show_search_suggestions)),
+ )
+
+private val searchSuggestionDescription: UiObject =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_show_search_suggestions_summary)),
+ )
+
+private val searchSuggestionLearnMoreLink: UiObject =
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/link"),
+ )
+
+private val searchSuggestionsSwitch: UiObject =
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/switchWidget"),
+ )
+
+private val urlAutocompleteSubMenu =
+ UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
+ .getChildByText(
+ UiSelector().text(getStringResource(R.string.preference_subitem_autocomplete)),
+ getStringResource(R.string.preference_subitem_autocomplete),
+ true,
+ )
+
+private val urlAutocompleteDefaultOption =
+ mDevice.findObject(
+ UiSelector()
+ .textContains(getStringResource(R.string.preference_state_on)),
+ )
+
+private val manageSitesSubMenu = onView(withText(R.string.preference_autocomplete_subitem_manage_sites))
+
+private val addCustomUrlButton = onView(withText(R.string.preference_autocomplete_action_add))
+
+private val customUrlText = onView(withId(R.id.domainView))
+
+private val saveButton = onView(withId(R.id.save))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSitePermissionsRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSitePermissionsRobot.kt
new file mode 100644
index 0000000000..e1bc361186
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsSitePermissionsRobot.kt
@@ -0,0 +1,166 @@
+/* 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.focus.activity.robots
+
+import androidx.test.uiautomator.UiSelector
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class SettingsSitePermissionsRobot {
+
+ fun verifySitePermissionsItems() {
+ assertTrue(autoplaySettings.waitForExists(waitingTime))
+ assertTrue(autoplayDefaultValue.exists())
+ assertTrue(cameraPermissionsSettings.exists())
+ assertTrue(cameraDefaultValue.exists())
+ assertTrue(locationPermissionsSettings.exists())
+ assertTrue(locationDefaultValue.exists())
+ assertTrue(microphonePermissionsSettings.exists())
+ assertTrue(microphoneDefaultValue.exists())
+ assertTrue(notificationPermissionsSettings.exists())
+ assertTrue(notificationDefaultValue.exists())
+ assertTrue(DRMContentPermissionsSettings.exists())
+ assertTrue(DRMContentDefaultValue.exists())
+ }
+
+ fun verifyAutoplaySection() {
+ assertTrue(autoplayAllowAudioAndVideoOption.exists())
+ assertTrue(autoplayBlockAudioOnlyOption.exists())
+ assertTrue(recommendedDescription.exists())
+ assertBlockAudioOnlyIsChecked()
+ assertTrue(blockAudioAndVideoOption.exists())
+ }
+
+ fun openAutoPlaySettings() {
+ autoplaySettings.waitForExists(waitingTime)
+ autoplaySettings.click()
+ }
+
+ fun openCameraPermissionsSettings() {
+ cameraPermissionsSettings.waitForExists(waitingTime)
+ cameraPermissionsSettings.click()
+ }
+
+ fun openLocationPermissionsSettings() {
+ locationPermissionsSettings.waitForExists(waitingTime)
+ locationPermissionsSettings.click()
+ }
+
+ fun verifyAskToAllowChecked() {
+ askToAllowRadioButton.waitForExists(waitingTime)
+ assertTrue(askToAllowRadioButton.isChecked)
+ }
+
+ fun verifyPermissionsStateSettings() {
+ assertTrue(askToAllowRadioButton.waitForExists(waitingTime))
+ assertTrue(blockedRadioButton.waitForExists(waitingTime))
+ }
+
+ fun verifyBlockedByAndroidState() {
+ assertTrue(blockedByAndroidInfo.waitForExists(waitingTime))
+ assertTrue(goToSettingsButton.waitForExists(waitingTime))
+ }
+
+ fun selectBlockAudioVideoAutoplay() {
+ blockAudioAndVideoOption.waitForExists(waitingTime)
+ blockAudioAndVideoOption.click()
+ }
+
+ fun selectAllowAudioVideoAutoplay() {
+ autoplayAllowAudioAndVideoOption.waitForExists(waitingTime)
+ autoplayAllowAudioAndVideoOption.click()
+ }
+
+ class Transition
+}
+
+private val autoplaySettings =
+ mDevice.findObject(
+ UiSelector().text(getStringResource(R.string.preference_autoplay)),
+ )
+
+private val autoplayDefaultValue =
+ mDevice.findObject(
+ UiSelector().text(getStringResource(R.string.preference_block_autoplay_audio_only)),
+ )
+
+private val autoplayAllowAudioAndVideoOption =
+ mDevice.findObject(UiSelector().text(getStringResource(R.string.preference_allow_audio_video_autoplay)))
+
+private val autoplayBlockAudioOnlyOption =
+ mDevice.findObject(UiSelector().text(getStringResource(R.string.preference_block_autoplay_audio_only)))
+
+private val recommendedDescription =
+ mDevice.findObject(UiSelector().text(getStringResource(R.string.preference_block_autoplay_audio_only_summary)))
+
+private val blockAudioAndVideoOption =
+ mDevice.findObject(UiSelector().text(getStringResource(R.string.preference_block_autoplay_audio_video)))
+
+private fun assertBlockAudioOnlyIsChecked() {
+ // the childSelector doesn't work anymore, so we are unable to find it by text
+ val radioButton =
+ mDevice.findObject(
+ UiSelector()
+ .checkable(true)
+ .index(1),
+ )
+ assertTrue(radioButton.isChecked)
+}
+
+private val locationPermissionsSettings =
+ mDevice.findObject(UiSelector().text("Location"))
+
+private val locationDefaultValue =
+ mDevice.findObject(UiSelector().text("Location"))
+ .getFromParent(UiSelector().text("Blocked by Android"))
+
+private val cameraPermissionsSettings =
+ mDevice.findObject(UiSelector().text("Camera"))
+
+private val cameraDefaultValue =
+ mDevice.findObject(UiSelector().text("Camera"))
+ .getFromParent(UiSelector().text("Blocked by Android"))
+
+private val microphonePermissionsSettings =
+ mDevice.findObject(UiSelector().text("Microphone"))
+
+private val microphoneDefaultValue =
+ mDevice.findObject(UiSelector().text("Microphone"))
+ .getFromParent(UiSelector().text("Blocked by Android"))
+
+private val notificationPermissionsSettings =
+ mDevice.findObject(UiSelector().text("Notification"))
+
+private val notificationDefaultValue =
+ mDevice.findObject(UiSelector().text("Notification"))
+ .getFromParent(UiSelector().text("Ask to allow"))
+
+private val DRMContentPermissionsSettings =
+ mDevice.findObject(UiSelector().text("DRM-controlled content"))
+
+private val DRMContentDefaultValue =
+ mDevice.findObject(UiSelector().text("DRM-controlled content"))
+ .getFromParent(UiSelector().text("Ask to allow"))
+
+private val askToAllowRadioButton =
+ // the childSelector doesn't work anymore, so we are unable to find it by text
+ mDevice.findObject(
+ UiSelector()
+ .checkable(true)
+ .index(0),
+ )
+
+private val blockedRadioButton =
+ mDevice.findObject(UiSelector().text("Blocked"))
+ .getFromParent(UiSelector().className("android.widget.RadioButton"))
+
+private val blockedByAndroidInfo =
+ mDevice.findObject(UiSelector().text("Blocked by Android"))
+
+private val goToSettingsButton =
+ mDevice.findObject(UiSelector().text("Go to Settings"))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SiteSecurityInfoSheetRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SiteSecurityInfoSheetRobot.kt
new file mode 100644
index 0000000000..243e0f8d8d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SiteSecurityInfoSheetRobot.kt
@@ -0,0 +1,75 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.RootMatchers.isDialog
+import androidx.test.espresso.matcher.ViewMatchers.hasSibling
+import androidx.test.espresso.matcher.ViewMatchers.isChecked
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiSelector
+import org.hamcrest.Matchers.allOf
+import org.hamcrest.Matchers.not
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+
+class SiteSecurityInfoSheetRobot {
+
+ fun verifySiteConnectionInfoIsSecure(isSecure: Boolean) {
+ assertTrue(site_security_info.waitForExists(waitingTime))
+ assertTrue(site_identity_title.exists())
+ assertTrue(site_identity_Icon.exists())
+ if (isSecure) {
+ assertTrue(site_security_info.text.equals("Connection is secure"))
+ } else {
+ assertTrue(site_security_info.text.equals("Connection is not secure"))
+ }
+ }
+
+ fun verifyTrackingProtectionIsEnabled(enabled: Boolean) {
+ if (enabled) {
+ trackingProtectionSwitch.check(matches(isChecked()))
+ } else {
+ trackingProtectionSwitch.check(matches(not(isChecked())))
+ }
+ }
+
+ class Transition {
+ fun closeSecurityInfoSheet(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ mDevice.pressBack()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun clickTrackingProtectionSwitch(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ trackingProtectionSwitch.perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+private val site_security_info = mDevice.findObject(UiSelector().resourceId("$packageName:id/security_info"))
+
+private val site_identity_title =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/site_title"))
+
+private val site_identity_Icon =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/site_favicon"))
+
+private val trackingProtectionSwitch =
+ onView(
+ allOf(
+ withId(R.id.switch_widget),
+ hasSibling(withText("Enhanced Tracking Protection")),
+ ),
+ ).inRoot(isDialog())
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/TabsTrayRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/TabsTrayRobot.kt
new file mode 100644
index 0000000000..d99d704b56
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/TabsTrayRobot.kt
@@ -0,0 +1,69 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.hasSibling
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.UiSelector
+import org.hamcrest.Matchers.allOf
+import org.hamcrest.Matchers.containsString
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+
+class TabsTrayRobot {
+ fun verifyTabsOrder(vararg tabTitle: String) {
+ for (tab in tabTitle.indices) {
+ assertTrue(
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/session_item")
+ .index(tab)
+ .childSelector(UiSelector().textContains(tabTitle[tab])),
+ ).waitForExists(waitingTime),
+ )
+ }
+ }
+
+ fun verifyCloseTabButton(tabTitle: String) = closeTabButton(tabTitle).check(matches(isDisplayed()))
+
+ class Transition {
+ fun selectTab(tabTitle: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ onView(withText(containsString(tabTitle))).perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun closeTab(tabTitle: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ closeTabButton(tabTitle).perform(click())
+ // waiting for the tab to be completely gone before trying other actions on the toolbar
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/mozac_browser_toolbar_url_view")
+ .textContains(tabTitle),
+ ).waitUntilGone(waitingTimeShort)
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+private fun closeTabButton(tabTitle: String) =
+ onView(
+ allOf(
+ withId(R.id.close_button),
+ hasSibling(withText(containsString(tabTitle))),
+ ),
+ )
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/ThreeDotMainMenuRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/ThreeDotMainMenuRobot.kt
new file mode 100644
index 0000000000..6bb650dc34
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/ThreeDotMainMenuRobot.kt
@@ -0,0 +1,238 @@
+/* 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.focus.activity.robots
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import junit.framework.TestCase.assertTrue
+import org.hamcrest.Matchers.allOf
+import org.junit.Assert.assertFalse
+import org.mozilla.focus.R
+import org.mozilla.focus.helpers.TestHelper.getStringResource
+import org.mozilla.focus.helpers.TestHelper.mDevice
+import org.mozilla.focus.helpers.TestHelper.packageName
+import org.mozilla.focus.helpers.TestHelper.progressBar
+import org.mozilla.focus.helpers.TestHelper.waitingTime
+import org.mozilla.focus.helpers.TestHelper.waitingTimeShort
+
+class ThreeDotMainMenuRobot {
+
+ fun verifyShareButtonExists() = assertTrue(shareBtn.exists())
+
+ fun verifyAddToHomeButtonExists() = assertTrue(addToHomeButton.exists())
+
+ fun verifyFindInPageExists() = findInPageButton.check(matches(isDisplayed()))
+
+ fun verifyOpenInButtonExists() = assertTrue(openInBtn.exists())
+
+ fun verifyRequestDesktopSiteExists() = assertTrue(requestDesktopSiteButton.exists())
+
+ fun verifyRequestDesktopSiteIsEnabled(expectedState: Boolean) {
+ if (expectedState) {
+ assertTrue(requestDesktopSiteButton.isChecked)
+ } else {
+ assertFalse(requestDesktopSiteButton.isChecked)
+ }
+ }
+
+ fun verifySettingsButtonExists() = settingsMenuButton().check(matches(isDisplayed()))
+
+ fun verifyReportSiteIssueButtonExists() {
+ // Report Site Issue lazily appears, so we need to wait
+ val reportSiteIssueButton = mDevice.wait(
+ Until.hasObject(
+ By.res("$packageName:id/mozac_browser_menu_menuView").hasDescendant(
+ By.text("Report Site Issue…"),
+ ),
+ ),
+ waitingTime,
+ )
+
+ assertTrue(reportSiteIssueButton)
+ }
+
+ fun verifyHelpPageLinkExists() = helpPageMenuLink.check(matches(isDisplayed()))
+
+ fun clickOpenInOption() {
+ openInBtn.waitForExists(waitingTime)
+ openInBtn.click()
+ }
+
+ fun verifyOpenInDialog() {
+ assertTrue(openInDialogTitle.waitForExists(waitingTime))
+ assertTrue(openWithList.waitForExists(waitingTime))
+ }
+
+ fun clickOpenInChrome() {
+ val chromeBrowser = mDevice.findObject(UiSelector().text("Chrome"))
+ if (chromeBrowser.exists()) {
+ chromeBrowser.click()
+ }
+ }
+
+ fun clickAddToShortcuts() {
+ addShortcutButton.waitForExists(waitingTimeShort)
+ addShortcutButton.click()
+ }
+
+ class Transition {
+ fun openSettings(
+ localizedText: String = getStringResource(R.string.menu_settings),
+ interact: SettingsRobot.() -> Unit,
+ ): SettingsRobot.Transition {
+ mDevice.findObject(UiSelector().text(localizedText)).waitForExists(waitingTime)
+ settingsMenuButton(localizedText)
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ SettingsRobot().interact()
+ return SettingsRobot.Transition()
+ }
+
+ fun openShareScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ shareBtn.waitForExists(waitingTime)
+ shareBtn.click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openAddToHSDialog(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition {
+ addToHomeButton.waitForExists(waitingTime)
+ addToHomeButton.click()
+
+ AddToHomeScreenRobot().interact()
+ return AddToHomeScreenRobot.Transition()
+ }
+
+ fun clickHelpPageLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ helpPageMenuLink
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun clickReloadButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ reloadButton.click()
+ progressBar.waitUntilGone(waitingTime)
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun clickStopLoadingButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ stopLoadingButton.click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun openFindInPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ findInPageButton.perform(click())
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun switchDesktopSiteMode(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ requestDesktopSiteButton.waitForExists(waitingTime)
+ requestDesktopSiteButton.click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun pressBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ backButton.click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+
+ fun pressForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
+ forwardButton.click()
+
+ BrowserRobot().interact()
+ return BrowserRobot.Transition()
+ }
+ }
+}
+
+private fun settingsMenuButton(localizedText: String = "Settings") =
+ onView(
+ allOf(withText(localizedText), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)),
+ )
+
+private val shareBtn = mDevice.findObject(
+ UiSelector()
+ .description("Share…"),
+)
+
+private val addShortcutButton =
+ mDevice.findObject(
+ UiSelector()
+ .text("Add to Shortcuts"),
+ )
+
+private val reloadButton = mDevice.findObject(
+ UiSelector()
+ .description("Reload website"),
+)
+
+private val stopLoadingButton = mDevice.findObject(
+ UiSelector()
+ .description("Stop loading website"),
+)
+
+private val addToHomeButton = mDevice.findObject(
+ UiSelector()
+ .text("Add to Home screen"),
+)
+
+private val findInPageButton = onView(withText("Find in Page"))
+
+private val helpPageMenuLink = onView(withText("Help"))
+
+private val openInBtn = mDevice.findObject(
+ UiSelector()
+ .text("Open in…"),
+)
+
+private val openInDialogTitle = mDevice.findObject(
+ UiSelector()
+ .text("Open in…"),
+)
+
+private val openWithList = mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/apps"),
+)
+
+private val requestDesktopSiteButton =
+ mDevice.findObject(
+ UiSelector()
+ .resourceId("$packageName:id/switch_widget"),
+ )
+
+private val backButton =
+ mDevice.findObject(
+ UiSelector()
+ .description("Navigate back"),
+ )
+
+private val forwardButton =
+ mDevice.findObject(
+ UiSelector()
+ .description("Navigate forward"),
+ )
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/Constants.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/Constants.kt
new file mode 100644
index 0000000000..81d273a294
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/Constants.kt
@@ -0,0 +1,9 @@
+/* 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.focus.helpers
+
+object Constants {
+ const val RETRY_COUNT = 3
+ const val LONG_CLICK_DURATION: Long = 5000
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/DeleteFilesHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/DeleteFilesHelper.kt
new file mode 100644
index 0000000000..85c2a76842
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/DeleteFilesHelper.kt
@@ -0,0 +1,52 @@
+/* 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.focus.helpers
+
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.provider.MediaStore
+import android.util.Log
+import androidx.core.net.toUri
+
+object DeleteFilesHelper {
+ fun deleteFileUsingDisplayName(context: Context, displayName: String): Boolean {
+ val uri = getUriFromDisplayName(context, displayName)
+ if (uri != null) {
+ val resolver = context.contentResolver
+ val selectionArgs = arrayOf(displayName)
+ resolver.delete(
+ uri,
+ MediaStore.Files.FileColumns.DISPLAY_NAME + "=?",
+ selectionArgs,
+ )
+ Log.d("TestLog", "Download file deleted")
+ return true
+ }
+ Log.d("TestLog", "Download file could not be found")
+ return false
+ }
+
+ private fun getUriFromDisplayName(context: Context, displayName: String): Uri? {
+ val projection = arrayOf(MediaStore.Files.FileColumns._ID)
+ val extUri: Uri = MediaStore.Files.getContentUri("external")
+ val cursor: Cursor = context.contentResolver.query(
+ extUri,
+ projection,
+ MediaStore.Files.FileColumns.DISPLAY_NAME + " LIKE ?",
+ arrayOf(displayName),
+ null,
+ )!!
+ cursor.moveToFirst()
+ return if (cursor.count > 0) {
+ val columnIndex: Int = cursor.getColumnIndex(projection[0])
+ val fileId: Long = cursor.getLong(columnIndex)
+ cursor.close()
+ "$extUri/$fileId".toUri()
+ } else {
+ null
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/EspressoHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/EspressoHelper.kt
new file mode 100644
index 0000000000..78a4b64cdb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/EspressoHelper.kt
@@ -0,0 +1,84 @@
+/* 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.focus.helpers
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.hasSibling
+import androidx.test.espresso.matcher.ViewMatchers.withChild
+import androidx.test.espresso.matcher.ViewMatchers.withParent
+import org.hamcrest.Description
+import org.hamcrest.Matcher
+import org.hamcrest.Matchers
+import org.hamcrest.TypeSafeMatcher
+import org.mozilla.focus.R
+
+/**
+ * Some convenient methods for testing Focus with espresso.
+ */
+object EspressoHelper {
+ fun hasCousin(matcher: Matcher): Matcher {
+ return withParent(
+ hasSibling(
+ withChild(
+ matcher,
+ ),
+ ),
+ )
+ }
+
+ @JvmStatic
+ fun openSettings() {
+ openMenu()
+ Espresso.onView(ViewMatchers.withId(R.id.settings))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .perform(ViewActions.click())
+ assertToolbarMatchesText(R.string.menu_settings)
+ }
+
+ @JvmStatic
+ fun openMenu() {
+ Espresso.onView(ViewMatchers.withId(R.id.menuView))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .perform(ViewActions.click())
+ }
+
+ @JvmStatic
+ fun assertToolbarMatchesText(@StringRes titleResource: Int) {
+ Espresso.onView(
+ Matchers.allOf(
+ ViewMatchers.withClassName(Matchers.endsWith("TextView")),
+ ViewMatchers.withParent(ViewMatchers.withId(R.id.toolbar)),
+ ),
+ )
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .check(ViewAssertions.matches(ViewMatchers.withText(titleResource)))
+ }
+
+ @JvmStatic
+ fun childAtPosition(
+ parentMatcher: Matcher,
+ position: Int,
+ ): Matcher {
+ return object : TypeSafeMatcher() {
+ override fun describeTo(description: Description) {
+ description.appendText("Child at position $position in parent ")
+ parentMatcher.describeTo(description)
+ }
+
+ public override fun matchesSafely(view: View): Boolean {
+ val parent = view.parent
+ return (
+ parent is ViewGroup && parentMatcher.matches(parent) &&
+ view == parent.getChildAt(position)
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/FeatureSettingsHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/FeatureSettingsHelper.kt
new file mode 100644
index 0000000000..b0bf3f3db0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/FeatureSettingsHelper.kt
@@ -0,0 +1,51 @@
+/* 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.focus.helpers
+
+import android.content.Context
+import androidx.test.platform.app.InstrumentationRegistry
+import org.mozilla.focus.cookiebanner.CookieBannerOption
+import org.mozilla.focus.ext.settings
+
+class FeatureSettingsHelper {
+ val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val settings = context.settings
+
+ // saving default values of feature flags
+ private var shouldShowCfrForTrackingProtection: Boolean =
+ settings.shouldShowCfrForTrackingProtection
+
+ fun setCfrForTrackingProtectionEnabled(enabled: Boolean) {
+ settings.shouldShowCfrForTrackingProtection = enabled
+ }
+
+ fun setShowStartBrowsingCfrEnabled(enabled: Boolean) {
+ settings.shouldShowStartBrowsingCfr = enabled
+ }
+
+ fun setCookieBannerReductionEnabled(enabled: Boolean) {
+ settings.isCookieBannerEnable = enabled
+ if (enabled) {
+ settings.saveCurrentCookieBannerOptionInSharePref(CookieBannerOption.CookieBannerRejectAll())
+ } else {
+ settings.saveCurrentCookieBannerOptionInSharePref(CookieBannerOption.CookieBannerDisabled())
+ }
+ }
+
+ fun setSearchWidgetDialogEnabled(enabled: Boolean) {
+ if (enabled) {
+ settings.addClearBrowsingSessions(4)
+ } else {
+ settings.addClearBrowsingSessions(10)
+ }
+ }
+
+ // Important:
+ // Use this after each test if you have modified these feature settings
+ // to make sure the app goes back to the default state
+ fun resetAllFeatureFlags() {
+ settings.shouldShowCfrForTrackingProtection = shouldShowCfrForTrackingProtection
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/HostScreencapScreenshotStrategy.java b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/HostScreencapScreenshotStrategy.java
new file mode 100644
index 0000000000..1a8428218e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/HostScreencapScreenshotStrategy.java
@@ -0,0 +1,116 @@
+/* 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.focus.helpers;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+import tools.fastlane.screengrab.ScreenshotCallback;
+import tools.fastlane.screengrab.ScreenshotStrategy;
+
+/**
+ * This ScreenshotStrategy implementation pings the host system (the computer the emulator is
+ * running on) to take a screenshot using "screencap" via adb. After that the screenshot is read
+ * from the internal app storage and passed back to fastlane/screengrab using the provided callback.
+ */
+public class HostScreencapScreenshotStrategy implements ScreenshotStrategy {
+ private static final int CONNECT_TIMEOUT = 1000;
+ private static final int READ_TIMEOUT = 5000;
+ private static final String HOST_LOOPBACK = "10.0.2.2";
+ private static final int PORT = 9771;
+
+ private UiDevice device;
+
+ public HostScreencapScreenshotStrategy(UiDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void takeScreenshot(String screenshotName, ScreenshotCallback screenshotCallback) {
+ device.waitForIdle();
+
+ takeScreenshotViaHost(screenshotName);
+
+ Bitmap bitmap = readScreenshotFromStorage();
+
+ if (bitmap == null) {
+ bitmap = createDummyScreenShot();
+ }
+
+ screenshotCallback.screenshotCaptured(screenshotName, bitmap);
+ }
+
+ private void takeScreenshotViaHost(String name) {
+ try {
+ final HttpURLConnection connection = (HttpURLConnection) new URL("http://" + HOST_LOOPBACK + ":" + PORT + "/" + name).openConnection();
+ connection.setConnectTimeout(CONNECT_TIMEOUT);
+ connection.setReadTimeout(READ_TIMEOUT);
+
+ try (final InputStream stream = connection.getInputStream()) {
+ final String response = readInput(stream);
+ stream.close();
+ connection.disconnect();
+
+ if (!"screenshot, exit=0".equals(response)) {
+ throw new RuntimeException("Taking screenshot failed, response: " + response);
+ }
+ }
+ } catch (ConnectException | SocketTimeoutException e) {
+ // We ignore those two exceptions because they occur if the http server on the host
+ // system is not running and we want to be able to execute this test even if we are
+ // not taking screenshots with fastlane. By running this test whenever we run the
+ // other UI tests we make sure that this test doesn't break without us noticing.
+ } catch (IOException e) {
+ throw new RuntimeException("Taking screenshot failed", e);
+ }
+ }
+
+ private Bitmap readScreenshotFromStorage() {
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ final String screenshotPath = new File(context.getFilesDir(), "temp_screen.png").getAbsolutePath();
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+ return BitmapFactory.decodeFile(screenshotPath);
+ }
+
+ private Bitmap createDummyScreenShot() {
+ final Bitmap bitmap = Bitmap.createBitmap(480, 800, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ canvas.drawColor(Color.MAGENTA);
+ return bitmap;
+ }
+
+ private static String readInput(InputStream stream) throws IOException {
+ try (final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
+ final StringBuilder builder = new StringBuilder();
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MainActivityTestRule.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MainActivityTestRule.kt
new file mode 100644
index 0000000000..b0d670421e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MainActivityTestRule.kt
@@ -0,0 +1,151 @@
+/* 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/. */
+
+@file:Suppress("DEPRECATION")
+
+package org.mozilla.focus.helpers
+
+import android.view.ViewConfiguration.getLongPressTimeout
+import androidx.annotation.CallSuper
+import androidx.test.espresso.intent.rule.IntentsTestRule
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiSelector
+import kotlinx.coroutines.runBlocking
+import mozilla.components.support.utils.ThreadUtils
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.helpers.TestHelper.getTargetContext
+import org.mozilla.focus.helpers.TestHelper.pressBackKey
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+import org.mozilla.focus.state.Screen
+
+// Basic Test rule with pref to skip the onboarding screen
+open class MainActivityFirstrunTestRule(
+ launchActivity: Boolean = true,
+ private val showFirstRun: Boolean,
+ private val showNewOnboarding: Boolean = true,
+ private val showStartBrowsingCfrVisibility: Boolean = false,
+) : ActivityTestRule(MainActivity::class.java, launchActivity) {
+ private val longTapUserPreference = getLongPressTimeout()
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @CallSuper
+ override fun beforeActivityLaunched() {
+ super.beforeActivityLaunched()
+ updateFirstRun(showFirstRun)
+ featureSettingsHelper.setShowStartBrowsingCfrEnabled(showStartBrowsingCfrVisibility)
+ featureSettingsHelper.setCookieBannerReductionEnabled(false)
+ setNewOnboarding(showNewOnboarding)
+ setLongTapTimeout(3000)
+ }
+
+ override fun afterActivityFinished() {
+ super.afterActivityFinished()
+
+ ThreadUtils.postToMainThread {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .targetContext
+ .applicationContext
+ .components
+ .tabsUseCases
+ .removeAllTabs()
+ }
+
+ featureSettingsHelper.resetAllFeatureFlags()
+ closeNotificationShade()
+ setLongTapTimeout(longTapUserPreference)
+ }
+}
+
+// Test rule that allows usage of Espresso Intents
+open class MainActivityIntentsTestRule(
+ launchActivity: Boolean = true,
+ private val showFirstRun: Boolean,
+ private val showStartBrowsingCfrVisibility: Boolean = false,
+ private val cookieBannerReductionEnabled: Boolean = false,
+) :
+ IntentsTestRule(MainActivity::class.java, launchActivity) {
+ private val longTapUserPreference = getLongPressTimeout()
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @CallSuper
+ override fun beforeActivityLaunched() {
+ super.beforeActivityLaunched()
+
+ updateFirstRun(showFirstRun)
+ featureSettingsHelper.setShowStartBrowsingCfrEnabled(showStartBrowsingCfrVisibility)
+ featureSettingsHelper.setCookieBannerReductionEnabled(cookieBannerReductionEnabled)
+ setLongTapTimeout(3000)
+ }
+
+ override fun afterActivityFinished() {
+ super.afterActivityFinished()
+ ThreadUtils.postToMainThread {
+ InstrumentationRegistry
+ .getInstrumentation()
+ .targetContext
+ .applicationContext
+ .components
+ .tabsUseCases
+ .removeAllTabs()
+ }
+
+ closeNotificationShade()
+ setLongTapTimeout(longTapUserPreference)
+ }
+}
+
+// Some tests will leave the notification shade open if they fail, needs to be closed before the next tests
+private fun closeNotificationShade() {
+ val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ if (mDevice.findObject(
+ UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"),
+ ).exists()
+ ) {
+ pressBackKey()
+ }
+}
+
+private fun updateFirstRun(showFirstRun: Boolean) {
+ val appContext = InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .applicationContext
+
+ val appStore = appContext.components.appStore
+ if (appStore.state.screen is Screen.FirstRun && !showFirstRun) {
+ hideFirstRun(appStore)
+ } else if (appStore.state.screen !is Screen.FirstRun && showFirstRun) {
+ showFirstRun(appStore)
+ }
+ appContext.settings.isFirstRun = showFirstRun
+}
+
+private fun showFirstRun(appStore: AppStore) {
+ val job = appStore.dispatch(
+ AppAction.ShowFirstRun,
+ )
+ runBlocking { job.join() }
+}
+
+private fun hideFirstRun(appStore: AppStore) {
+ val job = appStore.dispatch(
+ AppAction.FinishFirstRun(tabId = null),
+ )
+ runBlocking { job.join() }
+}
+
+private fun setNewOnboarding(enabled: Boolean) {
+ getTargetContext.settings.isNewOnboardingEnable = enabled
+}
+
+// changing the device preference for Touch and Hold delay, to avoid long-clicks instead of a single-click
+private fun setLongTapTimeout(delay: Int) {
+ val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ mDevice.executeShellCommand("settings put secure long_press_timeout $delay")
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockLocationUpdatesRule.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockLocationUpdatesRule.kt
new file mode 100644
index 0000000000..5ed7ae4d51
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockLocationUpdatesRule.kt
@@ -0,0 +1,113 @@
+/* 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.focus.helpers
+
+import android.content.Context
+import android.location.Location
+import android.location.LocationManager
+import android.os.Build
+import android.os.SystemClock
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import org.junit.rules.ExternalResource
+import org.mozilla.focus.helpers.TestHelper.executeShellCommandBlocking
+import java.util.Date
+import kotlin.random.Random
+
+private const val mockProviderName = LocationManager.GPS_PROVIDER
+
+/**
+ * Rule that sets up a mock location provider that can inject location samples
+ * straight to the device that the test is running on.
+ *
+ * Credit to the mapbox team
+ * https://github.com/mapbox/mapbox-navigation-android/blob/87fab7ea1152b29533ee121eaf6c05bc202adf02/libtesting-ui/src/main/java/com/mapbox/navigation/testing/ui/MockLocationUpdatesRule.kt
+ *
+ */
+class MockLocationUpdatesRule : ExternalResource() {
+ private val instrumentation = getInstrumentation()
+ private val appContext = (ApplicationProvider.getApplicationContext() as Context)
+ val latitude = Random.nextDouble(-90.0, 90.0)
+ val longitude = Random.nextDouble(-180.0, 180.0)
+
+ private val locationManager: LocationManager by lazy {
+ (appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager)
+ }
+
+ override fun before() {
+ /* ADB command to enable the mock location setting on the device.
+ * Will not be turned back off due to limitations on knowing its initial state.
+ */
+ instrumentation.uiAutomation.executeShellCommandBlocking(
+ "appops set " +
+ appContext.packageName +
+ " android:mock_location allow",
+ )
+
+ // To mock locations we need a location provider, so we generate and set it here.
+ try {
+ locationManager.addTestProvider(
+ mockProviderName,
+ false,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ 3,
+ 2,
+ )
+ } catch (ex: Exception) {
+ // unstable
+ Log.w("MockLocationUpdatesRule", "addTestProvider failed")
+ }
+ locationManager.setTestProviderEnabled(mockProviderName, true)
+ }
+
+ // Cleaning up the location provider after the test.
+ override fun after() {
+ locationManager.setTestProviderEnabled(mockProviderName, false)
+ locationManager.removeTestProvider(mockProviderName)
+ }
+
+ /**
+ * Generate a valid mock location data and set with the help of a test provider.
+ *
+ * @param modifyLocation optional callback for modifying the constructed location before setting it.
+ */
+ fun setMockLocation(modifyLocation: (Location.() -> Unit)? = null) {
+ check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ "MockLocationUpdatesRule is supported only on Android devices " +
+ "running version >= Build.VERSION_CODES.M"
+ }
+
+ val location = Location(mockProviderName)
+ location.time = Date().time
+ location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
+ location.accuracy = 5f
+ location.altitude = 0.0
+ location.bearing = 0f
+ location.speed = 5f
+ location.latitude = latitude
+ location.longitude = longitude
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ location.verticalAccuracyMeters = 5f
+ location.bearingAccuracyDegrees = 5f
+ location.speedAccuracyMetersPerSecond = 5f
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ location.elapsedRealtimeUncertaintyNanos = 0.0
+ }
+
+ modifyLocation?.let {
+ location.apply(it)
+ }
+
+ locationManager.setTestProviderLocation(mockProviderName, location)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockWebServerHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockWebServerHelper.kt
new file mode 100644
index 0000000000..0679e6282c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/MockWebServerHelper.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.focus.helpers
+
+import android.os.Handler
+import android.os.Looper
+import androidx.core.net.toUri
+import androidx.test.platform.app.InstrumentationRegistry
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import okhttp3.mockwebserver.RecordedRequest
+import okio.Buffer
+import okio.source
+import java.io.IOException
+import java.io.InputStream
+
+object MockWebServerHelper {
+ /**
+ * A [MockWebServer] [Dispatcher] that will return Android assets in the body of requests.
+ *
+ * If the dispatcher is unable to read a requested asset, it will fail the test by throwing an
+ * Exception on the main thread.
+ *
+ */
+ const val HTTP_OK = 200
+ const val HTTP_NOT_FOUND = 404
+
+ class AndroidAssetDispatcher : Dispatcher() {
+ private val mainThreadHandler = Handler(Looper.getMainLooper())
+
+ override fun dispatch(request: RecordedRequest): MockResponse {
+ val assetManager = InstrumentationRegistry.getInstrumentation().context.assets
+ try {
+ val pathWithoutQueryParams = request.path!!.drop(1).toUri().path
+ assetManager.open(pathWithoutQueryParams!!).use { inputStream ->
+ return fileToResponse(pathWithoutQueryParams, inputStream)
+ }
+ } catch (e: IOException) { // e.g. file not found.
+ // We're on a background thread so we need to forward the exception to the main thread.
+ mainThreadHandler.postAtFrontOfQueue { throw e }
+ return MockResponse().setResponseCode(HTTP_NOT_FOUND)
+ }
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun fileToResponse(path: String, file: InputStream): MockResponse {
+ return MockResponse()
+ .setResponseCode(HTTP_OK)
+ .setBody(fileToBytes(file)!!)
+ .addHeader("content-type: " + contentType(path))
+ }
+
+ @Throws(IOException::class)
+ private fun fileToBytes(file: InputStream): Buffer? {
+ val result = Buffer()
+ result.writeAll(file.source())
+ return result
+ }
+
+ private fun contentType(path: String): String? {
+ return when {
+ path.endsWith(".png") -> "image/png"
+ path.endsWith(".jpg") -> "image/jpeg"
+ path.endsWith(".jpeg") -> "image/jpeg"
+ path.endsWith(".gif") -> "image/gif"
+ path.endsWith(".svg") -> "image/svg+xml"
+ path.endsWith(".html") -> "text/html; charset=utf-8"
+ path.endsWith(".txt") -> "text/plain; charset=utf-8"
+ else -> "application/octet-stream"
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/RetryTestRule.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/RetryTestRule.kt
new file mode 100644
index 0000000000..e854df2496
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/RetryTestRule.kt
@@ -0,0 +1,44 @@
+/* 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.focus.helpers
+
+import androidx.test.espresso.NoMatchingViewException
+import androidx.test.uiautomator.UiObjectNotFoundException
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import java.lang.AssertionError
+
+class RetryTestRule(private val retryCount: Int = 5) : TestRule {
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return statement {
+ for (i in 1..retryCount) {
+ try {
+ base.evaluate()
+ break
+ } catch (t: AssertionError) {
+ if (i == retryCount) {
+ throw t
+ }
+ } catch (t: UiObjectNotFoundException) {
+ if (i == retryCount) {
+ throw t
+ }
+ } catch (t: NoMatchingViewException) {
+ if (i == retryCount) {
+ throw t
+ }
+ }
+ }
+ }
+ }
+
+ private inline fun statement(crossinline eval: () -> Unit): Statement {
+ return object : Statement() {
+ override fun evaluate() = eval()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/StringsHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/StringsHelper.kt
new file mode 100644
index 0000000000..affe13e4ee
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/StringsHelper.kt
@@ -0,0 +1,27 @@
+/* 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.focus.helpers
+
+object StringsHelper {
+ const val FR_ENGLISH_LOCALE = "English (United States)"
+ const val FR_LANGUAGE_MENU = "Langue"
+ const val FR_SETTINGS = "Paramètres"
+ const val FR_GENERAL_HEADING = "Général"
+ const val FR_LANGUAGE_SYSTEM_DEFAULT = "Valeur par défaut du système"
+
+ const val EN_LANGUAGE_MENU_HEADING = "Language"
+ const val EN_AFRIKAANS_LOCALE = "Afrikaans"
+
+ const val AF_LANGUAGE_MENU = "Taal"
+ const val AF_SETTINGS = "Instellings"
+ const val AF_HELP = "Hulp"
+ const val AF_GENERAL_HEADING = "Algemeen"
+ const val AF_LANGUAGE_SYSTEM_DEFAULT = "System default"
+
+ // App package names
+ const val GMAIL_APP = "com.google.android.gm"
+ const val PHONE_APP = "com.android.dialer"
+ const val GOOGLE_PHOTOS = "com.google.android.apps.photos"
+ const val GOOGLE_CHROME = "com.google.android.apps.chrome"
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestAssetHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestAssetHelper.kt
new file mode 100644
index 0000000000..27247458ca
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestAssetHelper.kt
@@ -0,0 +1,71 @@
+/* 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.focus.helpers
+
+import okhttp3.mockwebserver.MockWebServer
+
+/**
+ * Helper for hosting web pages locally for testing purposes.
+ */
+object TestAssetHelper {
+ data class TestAsset(val url: String, val content: String, val title: String)
+
+ /**
+ * Hosts simple websites, found at androidTest/assets/tab[1|2|3].html
+ * Returns a list of TestAsset, which can be used to navigate to each and
+ * assert that the correct information is being displayed.
+ *
+ * Content for these pages all follow the same pattern. See [tab1.html] for
+ * content implementation details.
+ */
+ fun getGenericTabAsset(server: MockWebServer, pageNum: Int): TestAsset {
+ val url = server.url("tab$pageNum.html").toString()
+ val content = "Tab $pageNum"
+ val title = "tab$pageNum"
+
+ return TestAsset(url, content, title)
+ }
+
+ fun getGenericAsset(server: MockWebServer): TestAsset {
+ val url = server.url("genericPage.html").toString()
+ val content = "focus test page"
+ val title = "GenericPage"
+
+ return TestAsset(url, content, title)
+ }
+
+ fun getHTMLControlsPageAsset(server: MockWebServer): TestAsset {
+ val url = server.url("htmlControls.html").toString()
+ val content = ""
+ val title = "Html_Control_Form"
+
+ return TestAsset(url, content, title)
+ }
+
+ fun getEnhancedTrackingProtectionAsset(server: MockWebServer, pageTitle: String): TestAsset {
+ val url = server.url("etpPages/$pageTitle.html").toString()
+ val content = ""
+
+ return TestAsset(url, content, pageTitle)
+ }
+
+ fun getImageTestAsset(server: MockWebServer): TestAsset {
+ val url = server.url("image_test.html").toString()
+
+ return TestAsset(url, "", "")
+ }
+
+ fun getStorageTestAsset(server: MockWebServer, pageTitle: String): TestAsset {
+ val url = server.url(pageTitle).toString()
+
+ return TestAsset(url, "", "")
+ }
+
+ fun getMediaTestAsset(server: MockWebServer, pageTitle: String): TestAsset {
+ val url = server.url("$pageTitle.html").toString()
+
+ return TestAsset(url, "", pageTitle)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestHelper.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestHelper.kt
new file mode 100644
index 0000000000..522c07de6c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/TestHelper.kt
@@ -0,0 +1,379 @@
+/* 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.focus.helpers
+
+import android.app.PendingIntent
+import android.app.UiAutomation
+import android.content.ActivityNotFoundException
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.os.Build
+import android.os.Build.VERSION.SDK_INT
+import android.os.storage.StorageManager
+import android.os.storage.StorageVolume
+import android.util.Log
+import android.view.KeyEvent
+import android.view.inputmethod.InputMethodManager
+import androidx.browser.customtabs.CustomTabColorSchemeParams
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON
+import androidx.core.net.toUri
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.web.sugar.Web
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiSelector
+import junit.framework.AssertionFailedError
+import mozilla.components.support.utils.ext.getApplicationInfoCompat
+import okio.Buffer
+import org.hamcrest.Matchers
+import org.hamcrest.Matchers.allOf
+import org.junit.Assert
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.IntentReceiverActivity
+import org.mozilla.focus.utils.IntentUtils
+import java.io.File
+import java.io.FileInputStream
+import java.io.IOException
+import java.io.InputStream
+import java.util.concurrent.TimeUnit
+
+@Suppress("TooManyFunctions")
+object TestHelper {
+ @JvmField
+ var mDevice = UiDevice.getInstance(getInstrumentation())
+ val waitingTime = TimeUnit.SECONDS.toMillis(15)
+ val pageLoadingTime = TimeUnit.SECONDS.toMillis(25)
+ val waitingTimeShort: Long = TimeUnit.SECONDS.toMillis(3)
+
+ private val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+ fun randomString(stringLength: Int) =
+ (1..stringLength)
+ .map { kotlin.random.Random.nextInt(0, charPool.size) }
+ .map(charPool::get)
+ .joinToString("")
+
+ @JvmStatic
+ val getTargetContext: Context = getInstrumentation().targetContext
+
+ @JvmStatic
+ val packageName: String = getTargetContext.packageName
+
+ @JvmStatic
+ val appName: String = getTargetContext.getString(R.string.app_name)
+
+ fun getStringResource(id: Int) = getTargetContext.resources.getString(id, appName)
+
+ fun verifySnackBarText(text: String) {
+ val snackbarText = mDevice.findObject(UiSelector().textContains(text))
+ assertTrue(snackbarText.waitForExists(waitingTime))
+ }
+
+ fun clickSnackBarActionButton(action: String) {
+ val snackbarActionButton =
+ onView(
+ allOf(
+ withId(R.id.snackbar_action),
+ withText(action),
+ ),
+ )
+ snackbarActionButton.perform(click())
+ }
+
+ fun waitUntilSnackBarGone() {
+ mDevice.findObject(UiSelector().resourceId("$appName:id/snackbar_layout"))
+ .waitUntilGone(waitingTime)
+ }
+
+ fun isPackageInstalled(packageName: String): Boolean {
+ return try {
+ val packageManager = getInstrumentation().context.packageManager
+ packageManager.getApplicationInfoCompat(packageName, 0).enabled
+ } catch (exception: PackageManager.NameNotFoundException) {
+ Log.d("TestLog", exception.message.toString())
+ false
+ }
+ }
+
+ fun restartApp(activity: MainActivityFirstrunTestRule) {
+ with(activity) {
+ finishActivity()
+ mDevice.waitForIdle()
+ launchActivity(null)
+ }
+ }
+
+ // exit to the main view
+ fun exitToTop() {
+ val homeScreen =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/landingLayout"))
+ var homeScreenVisible = false
+ while (!homeScreenVisible) {
+ mDevice.pressBack()
+ homeScreenVisible = homeScreen.waitForExists(2000)
+ }
+ }
+
+ // exit to the browser view
+ fun exitToBrowser() {
+ val browserScreen =
+ mDevice.findObject(UiSelector().resourceId("$packageName:id/main_content"))
+ var browserScreenVisible = false
+ while (!browserScreenVisible) {
+ mDevice.pressBack()
+ browserScreenVisible = browserScreen.waitForExists(2000)
+ }
+ }
+
+ fun setNetworkEnabled(enabled: Boolean) {
+ when (enabled) {
+ true -> {
+ mDevice.executeShellCommand("svc data enable")
+ mDevice.executeShellCommand("svc wifi enable")
+ }
+
+ false -> {
+ mDevice.executeShellCommand("svc data disable")
+ mDevice.executeShellCommand("svc wifi disable")
+ }
+ }
+ mDevice.waitForIdle(waitingTime)
+ }
+
+ // verifies localized strings in different UIs
+ fun verifyTranslatedTextExists(text: String) =
+ assertTrue(mDevice.findObject(UiSelector().text(text)).waitForExists(waitingTime))
+
+ fun openAppFromExternalLink(url: String) {
+ val intent = Intent().apply {
+ action = Intent.ACTION_VIEW
+ data = url.toUri()
+ `package` = packageName
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ try {
+ getTargetContext.startActivity(intent)
+ } catch (ex: ActivityNotFoundException) {
+ intent.setPackage(null)
+ getTargetContext.startActivity(intent)
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+ fun verifyDownloadedFileOnStorage(fileName: String) {
+ val storageManager =
+ getInstrumentation().targetContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager
+ val storageVolumes = storageManager.storageVolumes
+ val storageVolume: StorageVolume = storageVolumes[0]
+ val file = File("${storageVolume.directory!!.path}/Download/$fileName")
+ assertTrue(file.exists())
+ }
+
+ // Method for granting app permission to access location/camera/mic
+ fun grantAppPermission() {
+ if (SDK_INT >= 23) {
+ mDevice.findObject(
+ UiSelector().textContains(
+ when (SDK_INT) {
+ Build.VERSION_CODES.R ->
+ "While using the app"
+ else -> "Allow"
+ },
+ ),
+ ).click()
+ }
+ }
+
+ fun UiAutomation.executeShellCommandBlocking(command: String) {
+ val output = executeShellCommand(command)
+ FileInputStream(output.fileDescriptor).use { it.readBytes() }
+ }
+
+ @JvmStatic
+ fun pressEnterKey() {
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_ENTER)
+ }
+
+ @JvmStatic
+ fun pressBackKey() {
+ mDevice.pressBack()
+ }
+
+ @JvmStatic
+ fun pressHomeKey() {
+ mDevice.pressHome()
+ }
+
+ fun createCustomTabIntent(
+ pageUrl: String,
+ customMenuItemLabel: String = "",
+ customActionButtonDescription: String = "",
+ ): Intent {
+ val appContext = getInstrumentation()
+ .targetContext
+ .applicationContext
+ val pendingIntent = PendingIntent.getActivity(appContext, 0, Intent(), IntentUtils.defaultIntentPendingFlags())
+
+ val customTabColorSchemeBuilder = CustomTabColorSchemeParams.Builder()
+ customTabColorSchemeBuilder.setToolbarColor(Color.MAGENTA)
+
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .addMenuItem(customMenuItemLabel, pendingIntent)
+ .setShareState(SHARE_STATE_ON)
+ .setActionButton(createTestBitmap(), customActionButtonDescription, pendingIntent, true)
+ .setDefaultColorSchemeParams(customTabColorSchemeBuilder.build())
+ .build()
+ customTabsIntent.intent.data = pageUrl.toUri()
+ customTabsIntent.intent.component = ComponentName(appContext, IntentReceiverActivity::class.java)
+ return customTabsIntent.intent
+ }
+
+ fun assertNativeAppOpens(appPackageName: String) {
+ try {
+ if (isPackageInstalled(packageName)) {
+ intended(toPackage(appPackageName))
+ }
+ } catch (e: AssertionFailedError) {
+ e.printStackTrace()
+ }
+ }
+
+ private fun createTestBitmap(): Bitmap {
+ val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ canvas.drawColor(Color.GREEN)
+ return bitmap
+ }
+
+ /**
+ * Wrapper for tests to run only when certain conditions are met.
+ * For example: this method will avoid accidentally running a test on GV versions where the feature is disabled.
+ */
+ fun runWithCondition(condition: Boolean, testBlock: () -> Unit) {
+ if (condition) {
+ testBlock()
+ }
+ }
+
+ /********* Old code locators - used only in Screenshots tests */
+ // wait for web area to be visible
+ @JvmStatic
+ fun waitForWebContent() {
+ Assert.assertTrue(geckoView.waitForExists(waitingTime))
+ }
+
+ @JvmField
+ var menuButton = Espresso.onView(
+ Matchers.allOf(
+ ViewMatchers.withId(R.id.menuView),
+ ViewMatchers.isDisplayed(),
+ ),
+ )
+
+ @JvmField
+ var permAllowBtn = mDevice.findObject(
+ UiSelector()
+ .textContains("Allow")
+ .clickable(true),
+ )
+
+ @JvmField
+ var webView = mDevice.findObject(
+ UiSelector()
+ .className("android.webkit.WebView")
+ .enabled(true),
+ )
+ var geckoView = mDevice.findObject(
+ UiSelector()
+ .resourceId(packageName + ":id/engineView")
+ .enabled(true),
+ )
+
+ @JvmField
+ var progressBar = mDevice.findObject(
+ UiSelector()
+ .resourceId(packageName + ":id/progress")
+ .enabled(true),
+ )
+
+ @JvmField
+ var AddtoHSmenuItem = mDevice.findObject(
+ UiSelector()
+ .resourceId(packageName + ":id/add_to_homescreen")
+ .enabled(true),
+ )
+
+ @JvmField
+ var AddtoHSCancelBtn = mDevice.findObject(
+ UiSelector()
+ .resourceId(packageName + ":id/addtohomescreen_dialog_cancel")
+ .enabled(true),
+ )
+
+ @JvmField
+ var securityInfoIcon = mDevice.findObject(
+ UiSelector()
+ .resourceId(packageName + ":id/security_info")
+ .enabled(true),
+ )
+
+ @JvmField
+ var identityState = mDevice.findObject(
+ UiSelector()
+ .resourceId(packageName + ":id/site_identity_state")
+ .enabled(true),
+ )
+
+ @JvmField
+ var shareAppList = mDevice.findObject(
+ UiSelector()
+ .resourceId("android:id/resolver_list")
+ .enabled(true),
+ )
+
+ @JvmStatic
+ @Throws(IOException::class)
+ fun readTestAsset(filename: String?): Buffer {
+ getInstrumentation().getContext().assets.open(filename!!)
+ .use { stream -> return readStreamFile(stream) }
+ }
+
+ @Throws(IOException::class)
+ fun readStreamFile(file: InputStream?): Buffer {
+ val buffer = Buffer()
+ buffer.write(file!!.readBytes())
+ return buffer
+ }
+
+ @JvmStatic
+ fun waitForWebSiteTitleLoad() {
+ Web.onWebView(ViewMatchers.withText("focus test page"))
+ }
+
+ @JvmStatic
+ fun verifyKeyboardVisibility(isExpectedToBeVisible: Boolean) {
+ val imm = getTargetContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ if (isExpectedToBeVisible) {
+ assertTrue(imm.isAcceptingText)
+ } else {
+ assertFalse(imm.isAcceptingText)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/ext/WaitNotNull.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/ext/WaitNotNull.kt
new file mode 100644
index 0000000000..33ca1a5d4a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/helpers/ext/WaitNotNull.kt
@@ -0,0 +1,20 @@
+/* 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.focus.helpers.ext
+
+import androidx.test.uiautomator.SearchCondition
+import androidx.test.uiautomator.UiDevice
+import org.junit.Assert.assertNotNull
+import org.mozilla.focus.helpers.TestHelper
+
+/**
+ * Blocks the test for [waitTime] miliseconds before continuing.
+ *
+ * Will cause the test to fail is the condition is not met before the timeout.
+ */
+fun UiDevice.waitNotNull(
+ searchCondition: SearchCondition<*>,
+ waitTime: Long = TestHelper.waitingTime,
+) = assertNotNull(wait(searchCondition, waitTime))
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/RecyclerViewIdlingResource.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/RecyclerViewIdlingResource.kt
new file mode 100644
index 0000000000..08edc53908
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/RecyclerViewIdlingResource.kt
@@ -0,0 +1,32 @@
+/* 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.focus.idlingResources
+
+import androidx.test.espresso.IdlingResource
+
+class RecyclerViewIdlingResource constructor(
+ private val recycler: androidx.recyclerview.widget.RecyclerView,
+ private val minItemCount: Int = 0,
+) : IdlingResource {
+
+ private var callback: IdlingResource.ResourceCallback? = null
+
+ override fun isIdleNow(): Boolean {
+ if (recycler.adapter != null && recycler.adapter!!.itemCount >= minItemCount) {
+ if (callback != null) {
+ callback!!.onTransitionToIdle()
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
+ this.callback = callback
+ }
+
+ override fun getName(): String {
+ return RecyclerViewIdlingResource::class.java.name + ":" + recycler.id
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/SessionLoadedIdlingResource.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/SessionLoadedIdlingResource.kt
new file mode 100644
index 0000000000..63cb622f0d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/idlingResources/SessionLoadedIdlingResource.kt
@@ -0,0 +1,48 @@
+/* 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.focus.idlingResources
+
+import androidx.test.espresso.IdlingResource
+import androidx.test.platform.app.InstrumentationRegistry
+import mozilla.components.browser.state.selector.selectedTab
+import org.mozilla.focus.FocusApplication
+
+/**
+ * An IdlingResource implementation that waits until the current session is not loading anymore.
+ * Only after loading has completed further actions will be performed.
+ */
+class SessionLoadedIdlingResource : IdlingResource {
+ private var resourceCallback: IdlingResource.ResourceCallback? = null
+
+ override fun getName(): String {
+ return SessionLoadedIdlingResource::class.java.simpleName
+ }
+
+ override fun isIdleNow(): Boolean {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as FocusApplication
+ val tab = context.components.store.state.selectedTab
+
+ return if (tab?.content?.loading == true) {
+ false
+ } else {
+ if (tab?.content?.progress == 100) {
+ invokeCallback()
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ private fun invokeCallback() {
+ if (resourceCallback != null) {
+ resourceCallback!!.onTransitionToIdle()
+ }
+ }
+
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
+ this.resourceCallback = callback
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/GlobalPrivacyControlTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/GlobalPrivacyControlTest.kt
new file mode 100644
index 0000000000..0675a59f70
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/GlobalPrivacyControlTest.kt
@@ -0,0 +1,60 @@
+/* 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.focus.privacy
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getStorageTestAsset
+import java.io.IOException
+
+/**
+ * Test that Global Privacy Control is always enabled in Focus.
+ */
+@RunWith(AndroidJUnit4ClassRunner::class)
+class GlobalPrivacyControlTest {
+ private lateinit var webServer: MockWebServer
+
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ }
+
+ @Test
+ fun gpcTest() {
+ val storageStartUrl = getStorageTestAsset(webServer, "global_privacy_control.html").url
+
+ searchScreen {
+ }.loadPage(storageStartUrl) {
+ verifyPageContent("GPC is enabled.")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/LocalSessionStorageTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/LocalSessionStorageTest.kt
new file mode 100644
index 0000000000..839f90f924
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/privacy/LocalSessionStorageTest.kt
@@ -0,0 +1,96 @@
+/* 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.focus.privacy
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.FeatureSettingsHelper
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.MockWebServerHelper
+import org.mozilla.focus.helpers.TestAssetHelper.getStorageTestAsset
+import org.mozilla.focus.testAnnotations.SmokeTest
+import java.io.IOException
+
+/**
+ * Make sure that session storage values are kept and written but removed at the end of a session.
+ */
+@RunWith(AndroidJUnit4ClassRunner::class)
+class LocalSessionStorageTest {
+ private lateinit var webServer: MockWebServer
+
+ private val featureSettingsHelper = FeatureSettingsHelper()
+
+ companion object {
+ const val SESSION_STORAGE_HIT = "Session storage has value"
+ const val LOCAL_STORAGE_MISS = "Local storage empty"
+ }
+
+ @get: Rule
+ var mActivityTestRule = MainActivityFirstrunTestRule(showFirstRun = false)
+
+ @Before
+ fun setUp() {
+ webServer = MockWebServer().apply {
+ dispatcher = MockWebServerHelper.AndroidAssetDispatcher()
+ start()
+ }
+ featureSettingsHelper.setCfrForTrackingProtectionEnabled(false)
+ featureSettingsHelper.setSearchWidgetDialogEnabled(false)
+ }
+
+ @After
+ fun tearDown() {
+ try {
+ webServer.shutdown()
+ } catch (e: IOException) {
+ throw AssertionError("Could not stop web server", e)
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun testLocalAndSessionStorageIsWrittenAndRemoved() {
+ val storageStartUrl = getStorageTestAsset(webServer, "storage_start.html").url
+ val storageCheckUrl = getStorageTestAsset(webServer, "storage_check.html").url
+
+ searchScreen {
+ }.loadPage(storageStartUrl) {
+ // Assert website is loaded and values are written.
+ verifyPageContent("Values written to storage")
+ }.openSearchBar {
+ // Now load the next website and assert that the values are still in the storage
+ }.loadPage(storageCheckUrl) {
+ verifyPageContent(SESSION_STORAGE_HIT)
+ verifyPageContent(LOCAL_STORAGE_MISS)
+ }.clearBrowsingData {}
+ searchScreen {
+ }.loadPage(storageCheckUrl) {
+ verifyPageContent("Session storage empty")
+ verifyPageContent("Local storage empty")
+ }
+ }
+
+ @SmokeTest
+ @Test
+ fun eraseCookiesTest() {
+ val storageStartUrl = getStorageTestAsset(webServer, "storage_start.html").url
+
+ searchScreen {
+ }.loadPage(storageStartUrl) {
+ verifyPageContent("No cookies set")
+ clickSetCookiesButton()
+ verifyPageContent("user=android")
+ }.clearBrowsingData {}
+ searchScreen {
+ }.loadPage(storageStartUrl) {
+ verifyPageContent("No cookies set")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/AllowListScreenshots.java b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/AllowListScreenshots.java
new file mode 100644
index 0000000000..8b1cdba03b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/AllowListScreenshots.java
@@ -0,0 +1,123 @@
+/* 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.focus.screenshots;
+
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.focus.R;
+import org.mozilla.focus.helpers.TestHelper;
+
+import java.io.IOException;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.pressImeActionButton;
+import static androidx.test.espresso.action.ViewActions.replaceText;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasFocus;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+@Ignore("See: https://github.com/mozilla-mobile/mobile-test-eng/issues/305")
+@RunWith(AndroidJUnit4.class)
+public class AllowListScreenshots extends ScreenshotTest {
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ private MockWebServer webServer;
+
+ @Before
+ public void setUpWebServer() throws IOException {
+ webServer = new MockWebServer();
+
+ // Test page
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("image_test.html")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("rabbit.jpg")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("download.jpg")));
+ // Download
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("image_test.html")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("rabbit.jpg")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("download.jpg")));
+ }
+
+ @After
+ public void tearDownWebServer() {
+ try {
+ webServer.close();
+ webServer.shutdown();
+ } catch (IOException e) {
+ throw new AssertionError("Could not stop web server", e);
+ }
+ }
+
+ @Test
+ public void takeScreenshotsOfMenuandAllowlist() throws UiObjectNotFoundException {
+ SystemClock.sleep(5000);
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(click(), replaceText(webServer.url("/").toString()));
+
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(pressImeActionButton());
+
+ device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/webview")
+ .enabled(true))
+ .waitForExists(waitingTime);
+
+ TestHelper.menuButton.perform(click());
+ Screengrab.screenshot("BrowserViewMenu");
+ onView(withId(R.id.enhanced_tracking)).perform(click());
+
+ // Open setting
+ onView(withId(R.id.menuView))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ onView(withId(R.id.settings))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ onView(withText(R.string.preference_privacy_and_security_header)).perform(click());
+
+ UiScrollable settingsView = new UiScrollable(new UiSelector().scrollable(true));
+ if (settingsView.exists()) { // On tablet, this will not be found
+ settingsView.scrollToEnd(5);
+ onView(withText(R.string.preference_exceptions)).perform(click());
+ }
+
+ onView(withId(R.id.removeAllExceptions))
+ .check(matches(isDisplayed()));
+ Screengrab.screenshot("ExceptionsDialog");
+ onView(withId(R.id.removeAllExceptions))
+ .perform(click());
+
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/BrowserScreenScreenshots.java b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/BrowserScreenScreenshots.java
new file mode 100644
index 0000000000..1fa82b2a83
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/BrowserScreenScreenshots.java
@@ -0,0 +1,301 @@
+/* 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.focus.screenshots;
+
+import android.os.SystemClock;
+
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.focus.R;
+import org.mozilla.focus.helpers.TestHelper;
+
+import java.io.IOException;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.pressImeActionButton;
+import static androidx.test.espresso.action.ViewActions.replaceText;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasFocus;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withResourceName;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.mozilla.focus.helpers.EspressoHelper.openSettings;
+
+@Ignore("See: https://github.com/mozilla-mobile/mobile-test-eng/issues/305")
+@RunWith(AndroidJUnit4.class)
+public class BrowserScreenScreenshots extends ScreenshotTest {
+
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ private MockWebServer webServer;
+
+ @Before
+ public void setUpWebServer() throws IOException {
+ webServer = new MockWebServer();
+
+ // Test page
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("image_test.html")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("rabbit.jpg")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("download.jpg")));
+ // Download
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("image_test.html")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("rabbit.jpg")));
+ webServer.enqueue(new MockResponse()
+ .setBody(TestHelper.readTestAsset("download.jpg")));
+ }
+
+ @After
+ public void tearDownWebServer() {
+ try {
+ webServer.close();
+ webServer.shutdown();
+ } catch (IOException e) {
+ throw new AssertionError("Could not stop web server", e);
+ }
+ }
+
+ @Test
+ public void takeScreenshotsOfBrowsingScreen() throws Exception {
+ SystemClock.sleep(5000);
+ takeScreenshotsOfBrowsingView();
+ takeScreenshotsOfOpenWithAndShare();
+ takeAddToHomeScreenScreenshot();
+ takeScreenshotofInsecureCon();
+ takeScreenshotOfFindDialog();
+ takeScreenshotOfTabsTrayAndErase();
+ takeScreenshotofSecureCon();
+ }
+
+ private void takeScreenshotsOfBrowsingView() {
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()));
+
+ // click yes, then go into search dialog and change to twitter, or create twitter engine
+ // If it does not exist (In order to get search unavailable dialog)
+ openSettings();
+ onView(withText(R.string.preference_category_search))
+ .perform(click());
+ onView(allOf(withText(R.string.preference_search_engine_label),
+ withResourceName("title")))
+ .perform(click());
+ onView(withText(R.string.preference_search_installed_search_engines))
+ .check(matches(isDisplayed()));
+
+ try {
+ onView(withText("Twitter"))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ } catch (NoMatchingViewException doesnotexist) {
+ final String addEngineLabel = getString(R.string.preference_search_add2);
+ onView(withText(addEngineLabel))
+ .check(matches(isEnabled()))
+ .perform(click());
+ onView(withId(R.id.edit_engine_name))
+ .check(matches(isEnabled()));
+ onView(withId(R.id.edit_engine_name))
+ .perform(replaceText("twitter"));
+ onView(withId(R.id.edit_search_string))
+ .perform(replaceText("https://twitter.com/search?q=%s"));
+ onView(withId(R.id.menu_save_search_engine))
+ .check(matches(isEnabled()))
+ .perform(click());
+ }
+
+ device.pressBack();
+ onView(allOf(withText(R.string.preference_search_engine_label),
+ withResourceName("title")))
+ .check(matches(isDisplayed()));
+ device.pressBack();
+ onView(withText(R.string.preference_category_search))
+ .check(matches(isDisplayed()));
+ device.pressBack();
+
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(click(), replaceText(webServer.url("/").toString()));
+ try {
+ onView(withId(R.id.enable_search_suggestions_button))
+ .check(matches(isDisplayed()));
+ Screengrab.screenshot("Enable_Suggestion_dialog");
+ onView(withId(R.id.enable_search_suggestions_button))
+ .perform(click());
+ Screengrab.screenshot("Suggestion_unavailable_dialog");
+ onView(withId(R.id.dismiss_no_suggestions_message))
+ .perform(click());
+ } catch (AssertionError dne) { }
+
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(pressImeActionButton());
+
+ device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/engineView")
+ .enabled(true))
+ .waitForExists(waitingTime);
+
+ onView(withId(R.id.mozac_browser_toolbar_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(withText(containsString(webServer.getHostName()))));
+ }
+
+ private void takeScreenshotsOfOpenWithAndShare() throws Exception {
+ /* Open_With View */
+ TestHelper.menuButton.perform(click());
+
+ UiObject openWithBtn = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/open_select_browser")
+ .enabled(true));
+ assertTrue(openWithBtn.waitForExists(waitingTime));
+ openWithBtn.click();
+ UiObject shareList = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/apps")
+ .enabled(true));
+ assertTrue(shareList.waitForExists(waitingTime));
+ Screengrab.screenshot("OpenWith_Dialog");
+
+ /* Share View */
+ UiObject shareBtn = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/share")
+ .enabled(true));
+ device.pressBack();
+ TestHelper.menuButton.perform(click());
+ assertTrue(shareBtn.waitForExists(waitingTime));
+ shareBtn.click();
+ TestHelper.shareAppList.waitForExists(waitingTime);
+ Screengrab.screenshot("Share_Dialog");
+
+ device.pressBack();
+ }
+
+ private void takeAddToHomeScreenScreenshot() throws UiObjectNotFoundException {
+ TestHelper.menuButton.perform(click());
+
+ TestHelper.AddtoHSmenuItem.waitForExists(waitingTime);
+ TestHelper.AddtoHSmenuItem.click();
+
+ TestHelper.AddtoHSCancelBtn.waitForExists(waitingTime);
+ Screengrab.screenshot("AddtoHSDialog");
+ TestHelper.AddtoHSCancelBtn.click();
+ }
+
+ private void takeScreenshotOfTabsTrayAndErase() throws Exception {
+ final UiObject mozillaImage = device.findObject(new UiSelector()
+ .resourceId("download")
+ .enabled(true));
+
+ UiObject imageMenuTitle = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/topPanel")
+ .enabled(true));
+ UiObject openNewTabTitle = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/design_menu_item_text")
+ .text(getString(R.string.mozac_feature_contextmenu_open_link_in_private_tab))
+ .enabled(true));
+ UiObject multiTabBtn = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/tabs")
+ .enabled(true));
+ UiObject eraseHistoryBtn = device.findObject(new UiSelector()
+ .text(getString(R.string.tabs_tray_action_erase))
+ .enabled(true));
+
+ assertTrue(mozillaImage.waitForExists(waitingTime));
+ mozillaImage.dragTo(mozillaImage, 7);
+ assertTrue(imageMenuTitle.waitForExists(waitingTime));
+ assertTrue(imageMenuTitle.exists());
+ Screengrab.screenshot("Image_Context_Menu");
+
+ //Open a new tab
+ openNewTabTitle.click();
+ TestHelper.mDevice.wait(Until.findObject(
+ By.res(TestHelper.getAppName(), "snackbar_text")), 5000);
+ Screengrab.screenshot("New_Tab_Popup");
+ TestHelper.mDevice.wait(Until.gone(
+ By.res(TestHelper.getAppName(), "snackbar_text")), 5000);
+
+ assertTrue(multiTabBtn.waitForExists(waitingTime));
+ multiTabBtn.click();
+ assertTrue(eraseHistoryBtn.waitForExists(waitingTime));
+ Screengrab.screenshot("Multi_Tab_Menu");
+
+ eraseHistoryBtn.click();
+
+ device.wait(Until.findObject(
+ By.res(TestHelper.getAppName(), "snackbar_text")), waitingTime);
+
+ Screengrab.screenshot("YourBrowsingHistoryHasBeenErased");
+ }
+
+ private void takeScreenshotOfFindDialog() throws Exception {
+ UiObject findinpageMenuItem = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/find_in_page")
+ .enabled(true));
+ UiObject findinpageCloseBtn = device.findObject(new UiSelector()
+ .resourceId(TestHelper.getAppName() + ":id/close_find_in_page")
+ .enabled(true));
+
+ TestHelper.menuButton.perform(click());
+ findinpageMenuItem.waitForExists(waitingTime);
+ findinpageMenuItem.click();
+
+ findinpageCloseBtn.waitForExists(waitingTime);
+ Screengrab.screenshot("Find_In_Page_Dialog");
+ findinpageCloseBtn.click();
+ }
+
+ private void takeScreenshotofInsecureCon() throws Exception {
+
+ TestHelper.securityInfoIcon.click();
+ TestHelper.identityState.waitForExists(waitingTime);
+ Screengrab.screenshot("insecure_connection");
+ device.pressBack();
+ }
+
+ // This test requires external internet connection
+ private void takeScreenshotofSecureCon() throws Exception {
+
+ // take the security info of google.com for https connection
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(click(), replaceText("www.google.com"), pressImeActionButton());
+ TestHelper.waitForWebContent();
+ TestHelper.progressBar.waitUntilGone(waitingTime);
+ TestHelper.securityInfoIcon.click();
+ TestHelper.identityState.waitForExists(waitingTime);
+ Screengrab.screenshot("secure_connection");
+ device.pressBack();
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ErrorPagesScreenshots.java b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ErrorPagesScreenshots.java
new file mode 100644
index 0000000000..aa95e0923e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ErrorPagesScreenshots.java
@@ -0,0 +1,96 @@
+/* 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.focus.screenshots;
+
+import android.os.Build;
+
+import androidx.test.espresso.web.webdriver.Locator;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.focus.R;
+import org.mozilla.focus.helpers.TestHelper;
+
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.pressImeActionButton;
+import static androidx.test.espresso.action.ViewActions.replaceText;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasFocus;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.web.sugar.Web.onWebView;
+import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement;
+import static androidx.test.espresso.web.webdriver.DriverAtoms.webClick;
+import static androidx.test.espresso.web.webdriver.DriverAtoms.webScrollIntoView;
+import static junit.framework.Assert.assertTrue;
+
+@Ignore("See: https://github.com/mozilla-mobile/mobile-test-eng/issues/305")
+@RunWith(AndroidJUnit4.class)
+public class ErrorPagesScreenshots extends ScreenshotTest {
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ private enum ErrorTypes {
+ ERROR_UNKNOWN (-1),
+ ERROR_HOST_LOOKUP (-2),
+ ERROR_CONNECT (-6),
+ ERROR_TIMEOUT (-8),
+ ERROR_REDIRECT_LOOP (-9),
+ ERROR_UNSUPPORTED_SCHEME (-10),
+ ERROR_FAILED_SSL_HANDSHAKE (-11),
+ ERROR_BAD_URL (-12),
+ ERROR_TOO_MANY_REQUESTS (-15);
+ private int value;
+
+ ErrorTypes(int value) {
+ this.value = value;
+ }
+ }
+
+ @Test
+ public void takeScreenshotsOfErrorPages() {
+ for (ErrorTypes error: ErrorTypes.values()) {
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(click(), replaceText("error:" + error.value), pressImeActionButton());
+
+ assertTrue(TestHelper.webView.waitForExists(waitingTime));
+ assertTrue(TestHelper.progressBar.waitUntilGone(waitingTime));
+
+ // Android O has an issue with using Locator.ID
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ UiObject tryAgainBtn = device.findObject(new UiSelector()
+ .resourceId("errorTryAgain")
+ .clickable(true));
+ assertTrue(tryAgainBtn.waitForExists(waitingTime));
+ } else {
+ onWebView()
+ .withElement(findElement(Locator.ID, "errorTitle"))
+ .perform(webClick());
+
+ onWebView()
+ .withElement(findElement(Locator.ID, "errorTryAgain"))
+ .perform(webScrollIntoView());
+ }
+
+ Screengrab.screenshot(error.name());
+
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .perform(click());
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/FirstRunScreenshots.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/FirstRunScreenshots.kt
new file mode 100644
index 0000000000..cdd8fd0465
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/FirstRunScreenshots.kt
@@ -0,0 +1,55 @@
+/* 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/. */
+@file:Suppress("DEPRECATION")
+
+package org.mozilla.focus.screenshots
+
+import android.os.SystemClock
+import androidx.test.rule.ActivityTestRule
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import tools.fastlane.screengrab.Screengrab
+import tools.fastlane.screengrab.locale.LocaleTestRule
+
+class FirstRunScreenshots : ScreenshotTest() {
+ @Rule
+ @JvmField
+ var mActivityTestRule: ActivityTestRule =
+ object : MainActivityFirstrunTestRule(true, true) {
+ }
+
+ @Rule
+ @JvmField
+ val localeTestRule = LocaleTestRule()
+
+ @Ignore
+ @Test
+ fun takeScreenshotsOfFirstrun() {
+ homeScreen {
+ verifyOnboardingFirstSlide()
+ device.waitForIdle()
+ SystemClock.sleep(5000)
+ Screengrab.screenshot("Onboarding_1_View")
+
+ clickOnboardingNextButton()
+
+ verifyOnboardingSecondSlide()
+ Screengrab.screenshot("Onboarding_2_View")
+
+ clickOnboardingNextButton()
+
+ verifyOnboardingThirdSlide()
+ Screengrab.screenshot("Onboarding_3_View")
+
+ clickOnboardingNextButton()
+
+ verifyOnboardingLastSlide()
+ Screengrab.screenshot("Onboarding_last_View")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/HomeScreenScreenshots.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/HomeScreenScreenshots.kt
new file mode 100644
index 0000000000..9f724f8b0c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/HomeScreenScreenshots.kt
@@ -0,0 +1,129 @@
+/* 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/. */
+@file:Suppress("DEPRECATION")
+
+package org.mozilla.focus.screenshots
+
+import android.os.SystemClock
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.ViewInteraction
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.rule.ActivityTestRule
+import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiSelector
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.TestHelper
+import org.mozilla.focus.helpers.TestHelper.waitForWebContent
+import tools.fastlane.screengrab.Screengrab
+import tools.fastlane.screengrab.locale.LocaleTestRule
+import java.util.Locale
+
+class HomeScreenScreenshots : ScreenshotTest() {
+ @Rule @JvmField
+ var mActivityTestRule: ActivityTestRule =
+ object : MainActivityFirstrunTestRule(true, false) {
+ }
+
+ @Rule @JvmField
+ val localeTestRule = LocaleTestRule()
+
+ @Before
+ fun setUp() {
+ mActivityTestRule.runOnUiThread {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
+ }
+ }
+
+ @Test
+ fun takeScreenshotsOfHomeScreen() {
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(ViewMatchers.hasFocus()))
+ SystemClock.sleep(5000)
+ editURLBar.click()
+ typeInSearchBar("")
+
+ Screengrab.screenshot("Home_View")
+ }
+
+ @Test
+ fun takeScreenshotShortCutsHomeScreen() {
+ homeScreen {
+ addSiteToShortCuts("mozilla.com")
+ addSiteToShortCuts("pocket.com")
+ addSiteToShortCuts("relay.com")
+ addSiteToShortCuts("monitor.firefox.com")
+ TestHelper.mDevice.waitForIdle()
+ Espresso.closeSoftKeyboard()
+ SystemClock.sleep(5000)
+ Screengrab.screenshot("ShortCuts")
+ }
+ }
+
+ @Test
+ fun openWebsiteFocus() {
+ var currentLocale: String = Locale.getDefault().getLanguage()
+
+ editURLBar.click()
+ SystemClock.sleep(1000)
+
+ typeInSearchBar("www.mozilla.org/" + currentLocale + "/firefox/browsers/mobile/focus/")
+ TestHelper.mDevice.waitForIdle()
+ TestHelper.mDevice.pressEnter()
+ waitForWebContent()
+ TestHelper.waitForWebSiteTitleLoad()
+
+ SystemClock.sleep(30000)
+ waitForWebContent()
+ Screengrab.screenshot("FocusWebsite")
+ }
+
+ private fun addSiteToShortCuts(website: String) {
+ editURLBar.click()
+ SystemClock.sleep(1000)
+ typeInSearchBar(website)
+ TestHelper.mDevice.pressEnter()
+ TestHelper.mDevice.waitForIdle()
+ menuButton.click()
+ TestHelper.mDevice.waitForIdle()
+ addToShortCuts()
+ TestHelper.mDevice.waitForIdle()
+ eraseButton.click()
+ TestHelper.mDevice.waitForIdle()
+ }
+ private val editURLBar: UiObject =
+ TestHelper.mDevice.findObject(
+ UiSelector().resourceId("${TestHelper.packageName}:id/mozac_browser_toolbar_edit_url_view"),
+ )
+
+ private fun typeInSearchBar(searchString: String) {
+ searchBar.clearTextField()
+ searchBar.setText(searchString)
+ }
+
+ private val searchBar =
+ TestHelper.mDevice.findObject(UiSelector().resourceId("${TestHelper.packageName}:id/mozac_browser_toolbar_edit_url_view"))
+
+ private val menuButton =
+ TestHelper.mDevice.findObject(UiSelector().resourceId("${TestHelper.packageName}:id/mozac_browser_toolbar_menu"))
+
+ private fun addToShortCuts(): ViewInteraction? = onView(ViewMatchers.withText(R.string.menu_add_to_shortcuts)).perform(
+ ViewActions.click(),
+ )
+
+ private val eraseButton =
+ TestHelper.mDevice.findObject(UiSelector().resourceId("${TestHelper.packageName}:id/erase"))
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/NotificationScreenshots.java b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/NotificationScreenshots.java
new file mode 100644
index 0000000000..3a7e63738b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/NotificationScreenshots.java
@@ -0,0 +1,104 @@
+/* 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.focus.screenshots;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.focus.R;
+import org.mozilla.focus.helpers.TestHelper;
+
+import java.io.IOException;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.pressImeActionButton;
+import static androidx.test.espresso.action.ViewActions.replaceText;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasFocus;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.Matchers.containsString;
+
+/**
+ * This test has been super flaky in the past so we moved it to its own test. This way we wat least
+ * only lose screenshots of this test in case it fails.
+ */
+@Ignore("See: https://github.com/mozilla-mobile/mobile-test-eng/issues/305")
+@RunWith(AndroidJUnit4.class)
+public class NotificationScreenshots extends ScreenshotTest {
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ private MockWebServer webServer;
+
+ @Before
+ public void setUpWebServer() throws IOException {
+ webServer = new MockWebServer();
+
+ // Test page
+ webServer.enqueue(new MockResponse().setBody(TestHelper.readTestAsset("genericPage.html")));
+ }
+
+ @After
+ public void tearDownWebServer() {
+ try {
+ webServer.close();
+ webServer.shutdown();
+ } catch (IOException e) {
+ throw new AssertionError("Could not stop web server", e);
+ }
+ }
+
+ @Test
+ public void takeScreenshotOfNotification() throws Exception {
+ onView(withId(R.id.mozac_browser_toolbar_edit_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(hasFocus()))
+ .perform(click(), replaceText(webServer.url("/").toString()), pressImeActionButton());
+
+ onView(withId(R.id.mozac_browser_toolbar_url_view))
+ .check(matches(isDisplayed()))
+ .check(matches(withText(containsString(webServer.getHostName()))));
+
+ final UiObject openAction = device.findObject(new UiSelector()
+ .descriptionContains(getString(R.string.notification_action_open))
+ .resourceId("android:id/action0")
+ .enabled(true));
+
+ device.openNotification();
+
+ try {
+ if (!openAction.waitForExists(waitingTime)) {
+ // The notification is not expanded. Let's expand it now.
+ device.findObject(new UiSelector()
+ .text(getString(R.string.app_name)))
+ .swipeDown(20);
+
+ assertTrue(openAction.waitForExists(waitingTime));
+ }
+
+ Screengrab.screenshot("DeleteHistory_NotificationBar");
+ } finally {
+ // Close notification tray again
+ device.pressBack();
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ScreenshotTest.java b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ScreenshotTest.java
new file mode 100644
index 0000000000..427d2f0661
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/ScreenshotTest.java
@@ -0,0 +1,94 @@
+package org.mozilla.focus.screenshots;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import androidx.annotation.StringRes;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mozilla.focus.activity.MainActivity;
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule;
+import org.mozilla.focus.idlingResources.SessionLoadedIdlingResource;
+
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
+
+/**
+ * Base class for tests that take screenshots.
+ */
+@Ignore("This test was written specifically for WebView and needs to be adapted for GeckoView, see: https://github.com/mozilla-mobile/mobile-test-eng/issues/305")
+public abstract class ScreenshotTest {
+ final long waitingTime = DateUtils.SECOND_IN_MILLIS * 10;
+
+ private Context targetContext;
+ private SessionLoadedIdlingResource loadingIdlingResource;
+
+ UiDevice device;
+
+ @Rule
+ public ActivityTestRule mActivityTestRule = new MainActivityFirstrunTestRule(true, false, true,false) {
+ @Override
+ protected void beforeActivityLaunched() {
+ super.beforeActivityLaunched();
+ }
+ };
+
+ @Rule
+ public TestRule screenshotOnFailureRule = new TestWatcher() {
+ @Override
+ protected void failed(Throwable e, Description description) {
+ // On error take a screenshot so that we can debug it easily
+ Screengrab.screenshot("FAILURE-" + getScreenshotName(description));
+ }
+
+ private String getScreenshotName(Description description) {
+ return description.getClassName().replace(".", "-")
+ + "_"
+ + description.getMethodName().replace(".", "-");
+ }
+ };
+
+ @Before
+ public void setUpScreenshots() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ targetContext = instrumentation.getTargetContext();
+ device = UiDevice.getInstance(instrumentation);
+
+ // Use this to switch between default strategy and HostScreencap strategy
+ Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
+ //Screengrab.setDefaultScreenshotStrategy(new HostScreencapScreenshotStrategy(device));
+
+ device.waitForIdle();
+ }
+
+ /* Disable idlingResources. This causes error when accessing Settings Dialog */
+ /*
+ @Before
+ public void setUpIdlingResources() {
+ loadingIdlingResource = new SessionLoadedIdlingResource();
+ IdlingRegistry.getInstance().register(loadingIdlingResource);
+ }
+
+ @After
+ public void tearDownIdlingResources() {
+ device.waitForIdle();
+ IdlingRegistry.getInstance().unregister(loadingIdlingResource);
+ }
+ */
+ String getString(@StringRes int resourceId) {
+ return targetContext.getString(resourceId).trim();
+ }
+
+ String getString(@StringRes int resourceId, Object... formatArgs) {
+ return targetContext.getString(resourceId, formatArgs).trim();
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/SettingsScreenshots.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/SettingsScreenshots.kt
new file mode 100644
index 0000000000..cf051b0f4c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/screenshots/SettingsScreenshots.kt
@@ -0,0 +1,71 @@
+/* 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/. */
+@file:Suppress("DEPRECATION")
+
+package org.mozilla.focus.screenshots
+
+import android.os.SystemClock
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
+import androidx.test.rule.ActivityTestRule
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.activity.robots.homeScreen
+import org.mozilla.focus.activity.robots.searchScreen
+import org.mozilla.focus.helpers.MainActivityFirstrunTestRule
+import org.mozilla.focus.helpers.TestHelper
+import tools.fastlane.screengrab.Screengrab
+import tools.fastlane.screengrab.locale.LocaleTestRule
+
+class SettingsScreenshots : ScreenshotTest() {
+ @Rule
+ @JvmField
+ var mActivityTestRule: ActivityTestRule =
+ object : MainActivityFirstrunTestRule(true, false) {
+ }
+
+ @Before
+ fun setUp() {
+ mActivityTestRule.runOnUiThread {
+ AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
+ }
+ }
+
+ @Rule
+ @JvmField
+ val localeTestRule = LocaleTestRule()
+
+ @Test
+ fun takeScreenshotOfSiteSettings() {
+ val pageUrl = "https://www.mozilla.org"
+
+ searchScreen {
+ }.loadPage(pageUrl) {
+ verifySiteTrackingProtectionIconShown()
+ SystemClock.sleep(5000)
+ TestHelper.waitForWebSiteTitleLoad()
+ TestHelper.waitForWebContent()
+ SystemClock.sleep(3000)
+ }.openSiteSettingsMenu {
+ SystemClock.sleep(5000)
+ Screengrab.screenshot("SiteSettingsSubMenu_View")
+ }
+ }
+
+ @Test
+ fun takeScreenshotOfSearchSettings() {
+ homeScreen {
+ }.openMainMenu {
+ }.openSettings {
+ verifySettingsMenuItems()
+ }.openSearchSettingsMenu {
+ verifySearchSettingsItems()
+ openSearchEngineSubMenu()
+ SystemClock.sleep(5000)
+ Screengrab.screenshot("SearchEngineSubMenu_View")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/testAnnotations/SmokeTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/testAnnotations/SmokeTest.kt
new file mode 100644
index 0000000000..7894012bea
--- /dev/null
+++ b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/testAnnotations/SmokeTest.kt
@@ -0,0 +1,13 @@
+/* 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.focus.testAnnotations
+
+/**
+ * A custom annotation to mark the smoke tests corresponding to the ones in TestRail:
+ * https://testrail.stage.mozaws.net/index.php?/suites/view/1028
+ */
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SmokeTest
diff --git a/mobile/android/focus-android/app/src/beta/res/drawable-v24/ic_splash_screen.xml b/mobile/android/focus-android/app/src/beta/res/drawable-v24/ic_splash_screen.xml
new file mode 100644
index 0000000000..dac3eb8620
--- /dev/null
+++ b/mobile/android/focus-android/app/src/beta/res/drawable-v24/ic_splash_screen.xml
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/beta/res/drawable/ic_splash_screen.png b/mobile/android/focus-android/app/src/beta/res/drawable/ic_splash_screen.png
new file mode 100644
index 0000000000..d19e90b7d9
Binary files /dev/null and b/mobile/android/focus-android/app/src/beta/res/drawable/ic_splash_screen.png differ
diff --git a/mobile/android/focus-android/app/src/beta/res/values-night/colors.xml b/mobile/android/focus-android/app/src/beta/res/values-night/colors.xml
new file mode 100644
index 0000000000..38c1ed8f3e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/beta/res/values-night/colors.xml
@@ -0,0 +1,7 @@
+
+
+
+ #f0f0f4
+
diff --git a/mobile/android/focus-android/app/src/debug/AndroidManifest.xml b/mobile/android/focus-android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000..a170dd5bbb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/DebugFocusApplication.kt b/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/DebugFocusApplication.kt
new file mode 100644
index 0000000000..c8b42487df
--- /dev/null
+++ b/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/DebugFocusApplication.kt
@@ -0,0 +1,40 @@
+/* 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.focus
+
+import androidx.preference.PreferenceManager
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import leakcanary.AppWatcher
+import leakcanary.LeakCanary
+import org.mozilla.focus.ext.application
+
+class DebugFocusApplication : FocusApplication() {
+
+ @OptIn(DelicateCoroutinesApi::class)
+ override fun setupLeakCanary() {
+ if (!AppWatcher.isInstalled) {
+ AppWatcher.manualInstall(
+ application = application,
+ watchersToInstall = AppWatcher.appDefaultWatchers(application),
+ )
+ }
+ GlobalScope.launch(Dispatchers.IO) {
+ val isEnabled = PreferenceManager.getDefaultSharedPreferences(applicationContext)
+ .getBoolean(getString(R.string.pref_key_leakcanary), true)
+ updateLeakCanaryState(isEnabled)
+ }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ override fun updateLeakCanaryState(isEnabled: Boolean) {
+ GlobalScope.launch(Dispatchers.IO) {
+ LeakCanary.showLeakDisplayActivityLauncherIcon(isEnabled)
+ LeakCanary.config = LeakCanary.config.copy(dumpHeap = isEnabled)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/utils/AdjustHelper.java b/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/utils/AdjustHelper.java
new file mode 100644
index 0000000000..b0aa2e1835
--- /dev/null
+++ b/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/utils/AdjustHelper.java
@@ -0,0 +1,14 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.focus.utils;
+
+import android.content.Context;
+
+public class AdjustHelper {
+ public static void setupAdjustIfNeeded(Context context) {
+ // DEBUG: No Adjust - This class has different implementations for all build types.
+ }
+}
diff --git a/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/web/Config.kt b/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/web/Config.kt
new file mode 100644
index 0000000000..33ddf545d7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/debug/java/org/mozilla/focus/web/Config.kt
@@ -0,0 +1,10 @@
+/* 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.focus.web
+
+object Config {
+ const val EXPERIMENT_DESCRIPTOR_GECKOVIEW_ENGINE = "use-gecko"
+ const val EXPERIMENT_DESCRIPTOR_HOME_SCREEN_TIPS = "use-homescreen-tips"
+}
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/android/focus-android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..a8b7928d2e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..3a67ba31af
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png b/mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..eda227ffe8
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..ef9c4579f1
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png b/mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..20f689eb47
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..caca7f639e
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..0962261d8d
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..9e430cd544
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..4a562102cf
Binary files /dev/null and b/mobile/android/focus-android/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/ic_launcher-playstore.png b/mobile/android/focus-android/app/src/focusBeta/ic_launcher-playstore.png
new file mode 100644
index 0000000000..0e543571d0
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/ic_launcher-playstore.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/java/org/mozilla/focus/utils/AdjustHelper.java b/mobile/android/focus-android/app/src/focusBeta/java/org/mozilla/focus/utils/AdjustHelper.java
new file mode 100644
index 0000000000..b0aa2e1835
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/java/org/mozilla/focus/utils/AdjustHelper.java
@@ -0,0 +1,14 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.focus.utils;
+
+import android.content.Context;
+
+public class AdjustHelper {
+ public static void setupAdjustIfNeeded(Context context) {
+ // DEBUG: No Adjust - This class has different implementations for all build types.
+ }
+}
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable-land/dark_background.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable-land/dark_background.xml
new file mode 100644
index 0000000000..b225a9d47a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable-land/dark_background.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/ic_launcher_foreground.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..ab0b4c11c5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/icon_foreground.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/icon_foreground.xml
new file mode 100644
index 0000000000..4be0323b4c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable-v24/icon_foreground.xml
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable/dark_background.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable/dark_background.xml
new file mode 100644
index 0000000000..0ee46fa4e7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable/dark_background.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable/ic_launcher_background.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..61f5b8183f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable/onboarding_logo.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable/onboarding_logo.xml
new file mode 100644
index 0000000000..0c7fbd412e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable/onboarding_logo.xml
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/drawable/wordmark2.xml b/mobile/android/focus-android/app/src/focusBeta/res/drawable/wordmark2.xml
new file mode 100644
index 0000000000..16f39c2eea
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/drawable/wordmark2.xml
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..c7743a9582
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher_round.xml b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..c7743a9582
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..899dfb375a
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..8f36b391ce
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..f56d854a80
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..60225a687f
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..f68b32b974
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..9a02a5aad2
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..8cb67765b9
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..221f27c4d2
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..86d19f8622
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..481a4155c4
Binary files /dev/null and b/mobile/android/focus-android/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/values/app.xml b/mobile/android/focus-android/app/src/focusBeta/res/values/app.xml
new file mode 100644
index 0000000000..f57980c9ad
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/values/app.xml
@@ -0,0 +1,7 @@
+
+
+
+ Firefox Focus Beta
+
diff --git a/mobile/android/focus-android/app/src/focusBeta/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/focusBeta/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..daaece8883
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusBeta/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusDebug/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/focusDebug/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..d9c6f9e185
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusDebug/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusNightly/res/values/app.xml b/mobile/android/focus-android/app/src/focusNightly/res/values/app.xml
new file mode 100644
index 0000000000..ed5943efac
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusNightly/res/values/app.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ Firefox Focus Nightly
+
diff --git a/mobile/android/focus-android/app/src/focusNightly/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/focusNightly/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..c5524dfc01
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusNightly/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusRelease/AndroidManifest.xml b/mobile/android/focus-android/app/src/focusRelease/AndroidManifest.xml
new file mode 100644
index 0000000000..39155c35b5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusRelease/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/utils/AdjustHelper.java b/mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/utils/AdjustHelper.java
new file mode 100644
index 0000000000..8181faf397
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/utils/AdjustHelper.java
@@ -0,0 +1,72 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.focus.utils;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.adjust.sdk.Adjust;
+import com.adjust.sdk.AdjustConfig;
+import com.adjust.sdk.LogLevel;
+
+import org.mozilla.focus.BuildConfig;
+import org.mozilla.focus.FocusApplication;
+import org.mozilla.focus.telemetry.GleanMetricsService;
+
+public class AdjustHelper {
+ public static void setupAdjustIfNeeded(FocusApplication application) {
+ // RELEASE: Enable Adjust - This class has different implementations for all build types.
+
+ //noinspection ConstantConditions
+ if (TextUtils.isEmpty(BuildConfig.ADJUST_TOKEN)) {
+ throw new IllegalStateException("No adjust token defined for release build");
+ }
+
+ if (!GleanMetricsService.isTelemetryEnabled(application)) {
+ return;
+ }
+
+ final AdjustConfig config = new AdjustConfig(application,
+ BuildConfig.ADJUST_TOKEN,
+ AdjustConfig.ENVIRONMENT_PRODUCTION,
+ true);
+
+ config.setLogLevel(LogLevel.SUPRESS);
+
+ Adjust.onCreate(config);
+
+ application.registerActivityLifecycleCallbacks(new AdjustLifecycleCallbacks());
+ }
+
+ private static final class AdjustLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
+ @Override
+ public void onActivityResumed(Activity activity) {
+ Adjust.onResume();
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ Adjust.onPause();
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
+
+ @Override
+ public void onActivityStarted(Activity activity) {}
+
+ @Override
+ public void onActivityStopped(Activity activity) {}
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {}
+ }
+}
diff --git a/mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/web/Config.kt b/mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/web/Config.kt
new file mode 100644
index 0000000000..33ddf545d7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusRelease/java/org/mozilla/focus/web/Config.kt
@@ -0,0 +1,10 @@
+/* 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.focus.web
+
+object Config {
+ const val EXPERIMENT_DESCRIPTOR_GECKOVIEW_ENGINE = "use-gecko"
+ const val EXPERIMENT_DESCRIPTOR_HOME_SCREEN_TIPS = "use-homescreen-tips"
+}
diff --git a/mobile/android/focus-android/app/src/focusRelease/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/focusRelease/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..c1f98fe8d6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/focusRelease/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klar/res/drawable/background_gradient_dark.xml b/mobile/android/focus-android/app/src/klar/res/drawable/background_gradient_dark.xml
new file mode 100644
index 0000000000..79a9e23abf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klar/res/drawable/background_gradient_dark.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klar/res/drawable/wordmark2.xml b/mobile/android/focus-android/app/src/klar/res/drawable/wordmark2.xml
new file mode 100644
index 0000000000..89c3112480
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klar/res/drawable/wordmark2.xml
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klar/res/values/app.xml b/mobile/android/focus-android/app/src/klar/res/values/app.xml
new file mode 100644
index 0000000000..7811e21146
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klar/res/values/app.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ Firefox Klar
+
+
+ Klar
+
diff --git a/mobile/android/focus-android/app/src/klarBeta/java/org/mozilla/focus/utils/AdjustHelper.java b/mobile/android/focus-android/app/src/klarBeta/java/org/mozilla/focus/utils/AdjustHelper.java
new file mode 100644
index 0000000000..b0aa2e1835
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarBeta/java/org/mozilla/focus/utils/AdjustHelper.java
@@ -0,0 +1,14 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.focus.utils;
+
+import android.content.Context;
+
+public class AdjustHelper {
+ public static void setupAdjustIfNeeded(Context context) {
+ // DEBUG: No Adjust - This class has different implementations for all build types.
+ }
+}
diff --git a/mobile/android/focus-android/app/src/klarBeta/res/drawable/onboarding_logo.xml b/mobile/android/focus-android/app/src/klarBeta/res/drawable/onboarding_logo.xml
new file mode 100644
index 0000000000..0c7fbd412e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarBeta/res/drawable/onboarding_logo.xml
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarBeta/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/klarBeta/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..1024f92407
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarBeta/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarDebug/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/klarDebug/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..b18e391208
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarDebug/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarNightly/drawable-v24/icon_foreground.xml b/mobile/android/focus-android/app/src/klarNightly/drawable-v24/icon_foreground.xml
new file mode 100644
index 0000000000..4be0323b4c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/drawable-v24/icon_foreground.xml
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarNightly/drawable/background_gradient_dark.xml b/mobile/android/focus-android/app/src/klarNightly/drawable/background_gradient_dark.xml
new file mode 100644
index 0000000000..79a9e23abf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/drawable/background_gradient_dark.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarNightly/drawable/icon_background.xml b/mobile/android/focus-android/app/src/klarNightly/drawable/icon_background.xml
new file mode 100644
index 0000000000..d2c843b554
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/drawable/icon_background.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarNightly/drawable/toolbar_url_background.xml b/mobile/android/focus-android/app/src/klarNightly/drawable/toolbar_url_background.xml
new file mode 100644
index 0000000000..ee5d803fd4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/drawable/toolbar_url_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher.xml b/mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..39b175e682
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher_round.xml b/mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..1b3296f41d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..57266ce663
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..200d210606
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..6035524f91
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..92290c2ddd
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..e62f9490c2
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..c8a9087966
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..efbf73d970
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..a241232589
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..40f1e0d060
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..00c87c538d
Binary files /dev/null and b/mobile/android/focus-android/app/src/klarNightly/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/klarNightly/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/klarNightly/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..c35341d8c1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarNightly/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/utils/AdjustHelper.java b/mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/utils/AdjustHelper.java
new file mode 100644
index 0000000000..b0aa2e1835
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/utils/AdjustHelper.java
@@ -0,0 +1,14 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.focus.utils;
+
+import android.content.Context;
+
+public class AdjustHelper {
+ public static void setupAdjustIfNeeded(Context context) {
+ // DEBUG: No Adjust - This class has different implementations for all build types.
+ }
+}
diff --git a/mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/web/Config.kt b/mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/web/Config.kt
new file mode 100644
index 0000000000..33ddf545d7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarRelease/java/org/mozilla/focus/web/Config.kt
@@ -0,0 +1,10 @@
+/* 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.focus.web
+
+object Config {
+ const val EXPERIMENT_DESCRIPTOR_GECKOVIEW_ENGINE = "use-gecko"
+ const val EXPERIMENT_DESCRIPTOR_HOME_SCREEN_TIPS = "use-homescreen-tips"
+}
diff --git a/mobile/android/focus-android/app/src/klarRelease/res/xml-v25/shortcuts.xml b/mobile/android/focus-android/app/src/klarRelease/res/xml-v25/shortcuts.xml
new file mode 100644
index 0000000000..c35341d8c1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/klarRelease/res/xml-v25/shortcuts.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/AndroidManifest.xml b/mobile/android/focus-android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..72926930ae
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/assets/error_style.css b/mobile/android/focus-android/app/src/main/assets/error_style.css
new file mode 100644
index 0000000000..82823afb5e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/assets/error_style.css
@@ -0,0 +1,172 @@
+/* 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/. */
+
+/* Below styling is mirroring the Android Components styling from
+https://github.com/mozilla-mobile/firefox-android/blob/main/android-components/components/browser/errorpages/src/main/assets/error_style.css */
+html,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ --moz-vertical-spacing: 10px;
+ --moz-background-height: 32px;
+}
+
+body {
+ background-size: 64px var(--moz-background-height);
+ /* background-size: 64px 32px; */
+ background-repeat: repeat-x;
+
+ background-color: #363B40;
+ color: #FFFFFF;
+ padding: 0 20px;
+
+ font-weight: 300;
+ font-size: 13px;
+ -moz-text-size-adjust: none;
+ font-family: sans-serif;
+}
+
+ul {
+ /* Shove the list indicator so that its left aligned, but use outside so that text
+ * doesn't don't wrap the text around it */
+ padding: 0 1em;
+ margin: 0;
+ list-style: round outside none;
+}
+
+#errorShortDesc,
+li:not(:last-of-type) {
+ /* Margins between the li and buttons below it won't be collapsed. Remove the bottom margin here. */
+ margin: var(--moz-vertical-spacing) 0;
+}
+
+h1 {
+ margin: 0;
+ /* Since this has an underline, use padding for vertical spacing rather than margin */
+ padding: var(--moz-vertical-spacing) 0;
+ font-weight: 300;
+ border-bottom: 1px solid #e0e2e5;
+}
+
+h2 {
+ font-size: small;
+ padding: 0;
+ margin: var(--moz-vertical-spacing) 0;
+}
+
+p {
+ margin: var(--moz-vertical-spacing) 0;
+}
+
+button {
+ /* Force buttons to display: block here to try and enfoce collapsing margins */
+ display: block;
+ width: 100%;
+ border: none;
+ padding: 1rem;
+ font-family: sans-serif;
+ background-color: #00A4DC;
+ color: #FFFFFF;
+ font-weight: 300;
+ border-radius: 2px;
+ background-image: none;
+ margin: var(--moz-vertical-spacing) 0 0;
+}
+
+.buttonSecondary{
+ /* Force buttons to display: block here to try and enforce collapsing margins */
+ display: block;
+ width: 100%;
+ border: none;
+ padding: 1rem;
+ font-family: sans-serif;
+ background-color: rgba(249, 249, 250, 0.1);
+ color: #FFFFFF;
+ font-weight: 300;
+ border-radius: 2px;
+ background-image: none;
+ margin: var(--moz-vertical-spacing) 0 0;
+}
+
+#errorPageContainer {
+ /* If the page is greater than 550px center the content.
+ * This number should be kept in sync with the media query for tablets below */
+ max-width: 550px;
+ margin: 0 auto;
+ transform: translateY(var(--moz-background-height));
+ padding-bottom: var(--moz-vertical-spacing);
+
+ min-height: calc(100% - var(--moz-background-height) - var(--moz-vertical-spacing));
+ display: flex;
+ flex-direction: column;
+}
+
+/* On large screen devices (hopefully a 7+ inch tablet, we already center content (see #errorPageContainer above).
+ Apply tablet specific styles here */
+@media (min-width: 550px) {
+ button {
+ min-width: 160px;
+ width: auto;
+ }
+
+ /* If the tablet is tall as well, add some padding to make content feel a bit more centered */
+ @media (min-height: 550px) {
+ #errorPageContainer {
+ padding-top: 64px;
+ min-height: calc(100% - 64px);
+ }
+ }
+}
+
+.advancedPanelButtonContainer {
+ background-color: rgba(128, 128, 147, 0.1);
+ display: flex;
+ justify-content: center;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+#advancedPanelBackButtonContainer {
+ padding-bottom: 0;
+}
+
+#advancedPanelContainer {
+ width: 100%;
+ left: 0;
+}
+
+.advanced-panel {
+ display: none;
+ background-color: #202023;
+ border: 1px solid rgba(249, 249, 250, 0.2);
+ margin: 48px auto;
+ min-width: 13em;
+ max-width: 52em;
+}
+
+.button-container {
+ display: flex;
+ flex-flow: row;
+}
+
+#badCertTechnicalInfo {
+ margin: 0em 1em 1em;
+ overflow: auto;
+ white-space: pre-line;
+}
+
+#advancedButton {
+ display: none;
+}
+
+#badCertAdvancedPanel {
+ display: none;
+}
+
+/* Below styling is Focus specific */
+a {
+ color: white;
+}
diff --git a/mobile/android/focus-android/app/src/main/assets/style.css b/mobile/android/focus-android/app/src/main/assets/style.css
new file mode 100644
index 0000000000..7dd085df52
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/assets/style.css
@@ -0,0 +1,43 @@
+/* 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/. */
+
+body, html {
+ background: #221F1F;
+ color: #FFFFFF;
+ font-family: sans-serif;
+ line-height: 24px;
+ font-size: 14px;
+}
+
+body{
+ padding-left: 24px;
+ padding-right: 24px;
+ margin-left: 0px;
+ margin-right: 0px;
+}
+
+a {
+ color: #0A9AF4;
+}
+
+/* Make only about page links ("learn more") white */
+.about a {
+ color: #FFFFFF;
+}
+
+p.subtitle {
+ text-align: center;
+ opacity: .7;
+ margin: 0;
+}
+
+img#wordmark {
+ /* We need to set the dp size here, because by default webview assumes the image is not
+ density specific (but since it's an android resource, we get a density specific version). */
+ width: 180px;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ padding-top: 24px;
+}
diff --git a/mobile/android/focus-android/app/src/main/ic_launcher-playstore.png b/mobile/android/focus-android/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000000..7de2611aff
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/ic_launcher-playstore.png differ
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/Components.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/Components.kt
new file mode 100644
index 0000000000..478a5a75fa
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/Components.kt
@@ -0,0 +1,321 @@
+/* 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.focus
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.app.NotificationManagerCompat
+import mozilla.components.browser.engine.gecko.cookiebanners.GeckoCookieBannersStorage
+import mozilla.components.browser.icons.BrowserIcons
+import mozilla.components.browser.state.engine.EngineMiddleware
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.engine.DefaultSettings
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.fetch.Client
+import mozilla.components.feature.app.links.AppLinksInterceptor
+import mozilla.components.feature.app.links.AppLinksUseCases
+import mozilla.components.feature.contextmenu.ContextMenuUseCases
+import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
+import mozilla.components.feature.downloads.DownloadMiddleware
+import mozilla.components.feature.downloads.DownloadsUseCases
+import mozilla.components.feature.media.MediaSessionFeature
+import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
+import mozilla.components.feature.prompts.PromptMiddleware
+import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
+import mozilla.components.feature.prompts.file.FileUploadsDirCleanerMiddleware
+import mozilla.components.feature.search.SearchUseCases
+import mozilla.components.feature.search.middleware.AdsTelemetryMiddleware
+import mozilla.components.feature.search.middleware.SearchMiddleware
+import mozilla.components.feature.search.region.RegionMiddleware
+import mozilla.components.feature.search.telemetry.ads.AdsTelemetry
+import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.session.SettingsUseCases
+import mozilla.components.feature.session.TrackingProtectionUseCases
+import mozilla.components.feature.tabs.CustomTabsUseCases
+import mozilla.components.feature.tabs.TabsUseCases
+import mozilla.components.feature.top.sites.PinnedSiteStorage
+import mozilla.components.feature.top.sites.TopSitesUseCases
+import mozilla.components.feature.webcompat.WebCompatFeature
+import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
+import mozilla.components.lib.crash.CrashReporter
+import mozilla.components.lib.crash.sentry.SentryService
+import mozilla.components.lib.crash.service.CrashReporterService
+import mozilla.components.lib.crash.service.GleanCrashReporterService
+import mozilla.components.lib.crash.service.MozillaSocorroService
+import mozilla.components.lib.publicsuffixlist.PublicSuffixList
+import mozilla.components.service.location.LocationService
+import mozilla.components.service.location.MozillaLocationService
+import mozilla.components.service.nimbus.NimbusApi
+import mozilla.components.support.base.android.NotificationsDelegate
+import mozilla.components.support.locale.LocaleManager
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.browser.BlockedTrackersMiddleware
+import org.mozilla.focus.cfr.CfrMiddleware
+import org.mozilla.focus.components.EngineProvider
+import org.mozilla.focus.downloads.DownloadService
+import org.mozilla.focus.engine.AppContentInterceptor
+import org.mozilla.focus.engine.ClientWrapper
+import org.mozilla.focus.engine.SanityCheckMiddleware
+import org.mozilla.focus.experiments.createNimbus
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.media.MediaSessionService
+import org.mozilla.focus.search.SearchFilterMiddleware
+import org.mozilla.focus.search.SearchMigration
+import org.mozilla.focus.state.AppState
+import org.mozilla.focus.state.AppStore
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.telemetry.GleanMetricsService
+import org.mozilla.focus.telemetry.TelemetryMiddleware
+import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog
+import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider
+import org.mozilla.focus.topsites.DefaultTopSitesStorage
+import org.mozilla.focus.utils.Settings
+import java.util.Locale
+
+/**
+ * Helper object for lazily initializing components.
+ */
+class Components(
+ context: Context,
+ private val engineOverride: Engine? = null,
+ private val clientOverride: Client? = null,
+) {
+ val appStore: AppStore by lazy {
+ AppStore(
+ AppState(
+ screen = if (context.settings.isFirstRun) Screen.FirstRun else Screen.Home,
+ topSites = emptyList(),
+ ),
+ )
+ }
+
+ private val notificationManagerCompat = NotificationManagerCompat.from(context)
+
+ val notificationsDelegate: NotificationsDelegate by lazy {
+ NotificationsDelegate(
+ notificationManagerCompat,
+ )
+ }
+
+ val appStartReasonProvider by lazy { AppStartReasonProvider() }
+
+ val startupActivityLog by lazy { StartupActivityLog() }
+
+ val startupStateProvider by lazy { StartupStateProvider(startupActivityLog, appStartReasonProvider) }
+
+ val settings by lazy { Settings(context) }
+
+ val fileUploadsDirCleaner: FileUploadsDirCleaner by lazy {
+ FileUploadsDirCleaner { context.cacheDir }
+ }
+
+ val engineDefaultSettings by lazy {
+ DefaultSettings(
+ requestInterceptor = AppContentInterceptor(context),
+ trackingProtectionPolicy = settings.createTrackingProtectionPolicy(),
+ javascriptEnabled = !settings.shouldBlockJavaScript(),
+ remoteDebuggingEnabled = settings.shouldEnableRemoteDebugging(),
+ webFontsEnabled = !settings.shouldBlockWebFonts(),
+ httpsOnlyMode = settings.getHttpsOnlyMode(),
+ preferredColorScheme = settings.getPreferredColorScheme(),
+ cookieBannerHandlingModePrivateBrowsing = settings.getCurrentCookieBannerOptionFromSharePref().mode,
+ )
+ }
+
+ val engine: Engine by lazy {
+ engineOverride ?: EngineProvider.createEngine(context, engineDefaultSettings).apply {
+ this@Components.settings.setupSafeBrowsing(this)
+ WebCompatFeature.install(this)
+ WebCompatReporterFeature.install(this, "focus-geckoview")
+ }
+ }
+
+ val client: ClientWrapper by lazy {
+ ClientWrapper(clientOverride ?: EngineProvider.createClient(context))
+ }
+
+ val trackingProtectionUseCases by lazy { TrackingProtectionUseCases(store, engine) }
+
+ val settingsUseCases by lazy { SettingsUseCases(engine, store) }
+
+ @Suppress("DEPRECATION")
+ private val locationService: LocationService by lazy {
+ if (BuildConfig.MLS_TOKEN.isEmpty()) {
+ LocationService.default()
+ } else {
+ MozillaLocationService(context, client.unwrap(), BuildConfig.MLS_TOKEN)
+ }
+ }
+
+ val store by lazy {
+ BrowserStore(
+ middleware = listOf(
+ TelemetryMiddleware(),
+ DownloadMiddleware(context, DownloadService::class.java),
+ SanityCheckMiddleware(),
+ // We are currently using the default location service. We should consider using
+ // an actual implementation:
+ // https://github.com/mozilla-mobile/focus-android/issues/4781
+ RegionMiddleware(context, locationService),
+ SearchMiddleware(context, migration = SearchMigration(context)),
+ SearchFilterMiddleware(),
+ PromptMiddleware(),
+ AdsTelemetryMiddleware(adsTelemetry),
+ BlockedTrackersMiddleware(context),
+ RecordingDevicesMiddleware(context, notificationsDelegate),
+ CfrMiddleware(context),
+ FileUploadsDirCleanerMiddleware(fileUploadsDirCleaner),
+ ) + EngineMiddleware.create(
+ engine,
+ // We are disabling automatic suspending of engine sessions under memory pressure.
+ // Instead we solely rely on GeckoView and the Android system to reclaim memory
+ // when needed. For details, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1752594
+ // https://github.com/mozilla-mobile/fenix/issues/12731
+ // https://github.com/mozilla-mobile/android-components/issues/11300
+ // https://github.com/mozilla-mobile/android-components/issues/11653
+ trimMemoryAutomatically = false,
+ ),
+ ).apply {
+ MediaSessionFeature(context, MediaSessionService::class.java, this).start()
+ }
+ }
+
+ /**
+ * The [CustomTabsServiceStore] holds global custom tabs related data.
+ */
+ val customTabsStore by lazy { CustomTabsServiceStore() }
+
+ val sessionUseCases: SessionUseCases by lazy { SessionUseCases(store) }
+
+ val tabsUseCases: TabsUseCases by lazy { TabsUseCases(store) }
+
+ val cookieBannerStorage: GeckoCookieBannersStorage by lazy { EngineProvider.createCookieBannerStorage(context) }
+
+ val publicSuffixList by lazy { PublicSuffixList(context) }
+
+ val searchUseCases: SearchUseCases by lazy {
+ SearchUseCases(store, tabsUseCases, sessionUseCases)
+ }
+
+ val contextMenuUseCases: ContextMenuUseCases by lazy { ContextMenuUseCases(store) }
+
+ val downloadsUseCases: DownloadsUseCases by lazy { DownloadsUseCases(store) }
+
+ val appLinksUseCases: AppLinksUseCases by lazy { AppLinksUseCases(context.applicationContext) }
+
+ val customTabsUseCases: CustomTabsUseCases by lazy { CustomTabsUseCases(store, sessionUseCases.loadUrl) }
+
+ val crashReporter: CrashReporter by lazy { createCrashReporter(context, notificationsDelegate) }
+
+ val metrics: GleanMetricsService by lazy { GleanMetricsService(context) }
+
+ val experiments: NimbusApi by lazy {
+ createNimbus(context, BuildConfig.NIMBUS_ENDPOINT)
+ }
+
+ val adsTelemetry: AdsTelemetry by lazy { AdsTelemetry() }
+
+ val searchTelemetry: InContentTelemetry by lazy { InContentTelemetry() }
+
+ val icons by lazy { BrowserIcons(context, client) }
+
+ val topSitesStorage by lazy { DefaultTopSitesStorage(PinnedSiteStorage(context)) }
+
+ val topSitesUseCases: TopSitesUseCases by lazy { TopSitesUseCases(topSitesStorage) }
+
+ val appLinksInterceptor by lazy {
+ AppLinksInterceptor(
+ context,
+ interceptLinkClicks = true,
+ launchInApp = {
+ context.settings.openLinksInExternalApp
+ },
+ )
+ }
+}
+
+private fun createCrashReporter(context: Context, notificationsDelegate: NotificationsDelegate): CrashReporter {
+ val services = mutableListOf()
+
+ if (BuildConfig.SENTRY_TOKEN.isNotEmpty()) {
+ val sentryService = SentryService(
+ context,
+ BuildConfig.SENTRY_TOKEN,
+ tags = mapOf(
+ "build_flavor" to BuildConfig.FLAVOR,
+ "build_type" to BuildConfig.BUILD_TYPE,
+ "locale_lang_tag" to getLocaleTag(context),
+ ),
+ environment = BuildConfig.BUILD_TYPE,
+ sendEventForNativeCrashes = false, // Do not send native crashes to Sentry
+ )
+
+ services.add(sentryService)
+ }
+
+ val socorroService = MozillaSocorroService(
+ context,
+ appName = "Focus",
+ version = org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION,
+ buildId = org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID,
+ vendor = org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR,
+ releaseChannel = org.mozilla.geckoview.BuildConfig.MOZ_UPDATE_CHANNEL,
+ )
+ services.add(socorroService)
+
+ val intent = Intent(context, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
+
+ val crashReportingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ PendingIntent.FLAG_MUTABLE
+ } else {
+ 0 // No flags. Default behavior.
+ }
+
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ crashReportingIntentFlags,
+ )
+
+ return CrashReporter(
+ context = context,
+ services = services,
+ telemetryServices = listOf(GleanCrashReporterService(context)),
+ promptConfiguration = CrashReporter.PromptConfiguration(
+ appName = context.resources.getString(R.string.app_name),
+ ),
+ shouldPrompt = CrashReporter.Prompt.ALWAYS,
+ enabled = true,
+ nonFatalCrashIntent = pendingIntent,
+ notificationsDelegate = notificationsDelegate,
+ )
+}
+
+private fun getLocaleTag(context: Context): String {
+ val currentLocale = LocaleManager.getCurrentLocale(context)
+ return if (currentLocale != null) {
+ currentLocale.toLanguageTag()
+ } else {
+ Locale.getDefault().toLanguageTag()
+ }
+}
+
+/**
+ * Returns the [Components] object from within a [Composable].
+ */
+val components: Components
+ @Composable
+ get() = LocalContext.current.components
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/FocusApplication.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/FocusApplication.kt
new file mode 100644
index 0000000000..0aa99630df
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/FocusApplication.kt
@@ -0,0 +1,238 @@
+/* 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.focus
+
+import android.content.Context
+import android.os.Build
+import android.os.StrictMode
+import android.util.Log.INFO
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.lifecycle.ProcessLifecycleOwner
+import androidx.preference.PreferenceManager
+import androidx.work.Configuration.Builder
+import androidx.work.Configuration.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import mozilla.components.support.base.facts.register
+import mozilla.components.support.base.log.Log
+import mozilla.components.support.base.log.sink.AndroidLogSink
+import mozilla.components.support.ktx.android.content.isMainProcess
+import mozilla.components.support.locale.LocaleAwareApplication
+import mozilla.components.support.rusthttp.RustHttpConfig
+import mozilla.components.support.rustlog.RustLog
+import mozilla.components.support.webextensions.WebExtensionSupport
+import org.mozilla.focus.biometrics.LockObserver
+import org.mozilla.focus.experiments.finishNimbusInitialization
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.navigation.StoreLink
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.session.VisibilityLifeCycleCallback
+import org.mozilla.focus.telemetry.FactsProcessor
+import org.mozilla.focus.telemetry.ProfilerMarkerFactProcessor
+import org.mozilla.focus.utils.AdjustHelper
+import org.mozilla.focus.utils.AppConstants
+import kotlin.coroutines.CoroutineContext
+
+@Suppress("TooManyFunctions")
+open class FocusApplication : LocaleAwareApplication(), Provider, CoroutineScope {
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ open val components: Components by lazy { Components(this) }
+
+ var visibilityLifeCycleCallback: VisibilityLifeCycleCallback? = null
+ private set
+
+ private val storeLink by lazy { StoreLink(components.appStore, components.store) }
+ private val lockObserver by lazy { LockObserver(this, components.store, components.appStore) }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ override fun onCreate() {
+ super.onCreate()
+
+ Log.addSink(AndroidLogSink("Focus"))
+ components.crashReporter.install(this)
+
+ if (isMainProcess()) {
+ initializeNimbus()
+
+ PreferenceManager.setDefaultValues(this, R.xml.settings, false)
+
+ setTheme(this)
+ components.engine.warmUp()
+
+ components.metrics.initialize(this)
+ FactsProcessor.initialize()
+ finishSetupMegazord()
+
+ ProfilerMarkerFactProcessor.create { components.engine.profiler }.register()
+
+ enableStrictMode()
+
+ AdjustHelper.setupAdjustIfNeeded(this@FocusApplication)
+
+ visibilityLifeCycleCallback = VisibilityLifeCycleCallback(this@FocusApplication)
+ registerActivityLifecycleCallbacks(visibilityLifeCycleCallback)
+ registerComponentCallbacks(visibilityLifeCycleCallback)
+
+ storeLink.start()
+
+ initializeWebExtensionSupport()
+
+ setupLeakCanary()
+
+ components.appStartReasonProvider.registerInAppOnCreate(this)
+ components.startupActivityLog.registerInAppOnCreate(this)
+
+ ProcessLifecycleOwner.get().lifecycle.addObserver(lockObserver)
+ GlobalScope.launch(Dispatchers.IO) {
+ // Remove stale temporary uploaded files.
+ components.fileUploadsDirCleaner.cleanUploadsDirectory()
+ }
+ }
+ }
+
+ override fun onConfigurationChanged(config: android.content.res.Configuration) {
+ applicationContext.resources.configuration.uiMode = config.uiMode
+ super.onConfigurationChanged(config)
+ }
+
+ protected open fun setupLeakCanary() {
+ // no-op, LeakCanary is disabled by default
+ }
+
+ open fun updateLeakCanaryState(isEnabled: Boolean) {
+ // no-op, LeakCanary is disabled by default
+ }
+
+ protected open fun initializeNimbus() {
+ beginSetupMegazord()
+
+ // This lazily constructs the Nimbus object…
+ val nimbus = components.experiments
+ // … which we then can populate the feature configuration.
+ FocusNimbus.initialize { nimbus }
+ }
+
+ /**
+ * Initiate Megazord sequence! Megazord Battle Mode!
+ *
+ * The application-services combined libraries are known as the "megazord". We use the default `full`
+ * megazord - it contains everything that fenix needs, and (currently) nothing more.
+ *
+ * Documentation on what megazords are, and why they're needed:
+ * - https://github.com/mozilla/application-services/blob/master/docs/design/megazords.md
+ * - https://mozilla.github.io/application-services/docs/applications/consuming-megazord-libraries.html
+ *
+ * This is the initialization of the megazord without setting up networking, i.e. needing the
+ * engine for networking. This should do the minimum work necessary as it is done on the main
+ * thread, early in the app startup sequence.
+ */
+ private fun beginSetupMegazord() {
+ // Note: Megazord.init() must be called as soon as possible ...
+ // Megazord.init()
+
+ // ... but RustHttpConfig.setClient() and RustLog.enable() can be called later.
+
+ RustLog.enable()
+ }
+
+ @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
+ private fun finishSetupMegazord() {
+ GlobalScope.launch(Dispatchers.IO) {
+ // We need to use an unwrapped client because native components do not support private
+ // requests.
+ @Suppress("Deprecation")
+ RustHttpConfig.setClient(lazy { components.client.unwrap() })
+
+ // Now viaduct (the RustHttp client) is initialized we can ask Nimbus to fetch
+ // experiments recipes from the server.
+ finishNimbusInitialization(components.experiments)
+ }
+ }
+
+ private fun setTheme(context: Context) {
+ val settings = context.settings
+ when {
+ settings.lightThemeSelected -> {
+ AppCompatDelegate.setDefaultNightMode(
+ AppCompatDelegate.MODE_NIGHT_NO,
+ )
+ }
+
+ settings.darkThemeSelected -> {
+ AppCompatDelegate.setDefaultNightMode(
+ AppCompatDelegate.MODE_NIGHT_YES,
+ )
+ }
+
+ settings.useDefaultThemeSelected -> {
+ setDefaultTheme()
+ }
+
+ // No theme setting selected, select the default value, follow device theme.
+ else -> {
+ setDefaultTheme()
+ settings.useDefaultThemeSelected = true
+ }
+ }
+ }
+
+ private fun setDefaultTheme() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ AppCompatDelegate.setDefaultNightMode(
+ AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
+ )
+ } else {
+ AppCompatDelegate.setDefaultNightMode(
+ AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY,
+ )
+ }
+ }
+
+ private fun enableStrictMode() {
+ // Android/WebView sometimes commit strict mode violations, see e.g.
+ // https://github.com/mozilla-mobile/focus-android/issues/660
+ if (AppConstants.isReleaseBuild || AppConstants.isBetaBuild) {
+ return
+ }
+
+ val threadPolicyBuilder = StrictMode.ThreadPolicy.Builder().detectAll()
+ val vmPolicyBuilder = StrictMode.VmPolicy.Builder()
+ .detectActivityLeaks()
+ .detectFileUriExposure()
+ .detectLeakedClosableObjects()
+ .detectLeakedRegistrationObjects()
+ .detectLeakedSqlLiteObjects()
+
+ threadPolicyBuilder.penaltyLog()
+ vmPolicyBuilder.penaltyLog()
+
+ StrictMode.setThreadPolicy(threadPolicyBuilder.build())
+ StrictMode.setVmPolicy(vmPolicyBuilder.build())
+ }
+
+ private fun initializeWebExtensionSupport() {
+ WebExtensionSupport.initialize(
+ components.engine,
+ components.store,
+ onNewTabOverride = { _, engineSession, url ->
+ components.tabsUseCases.addTab(
+ url = url,
+ selectTab = true,
+ engineSession = engineSession,
+ private = true,
+ )
+ },
+ )
+ }
+
+ override val workManagerConfiguration = Builder().setMinimumLoggingLevel(INFO).build()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CrashListActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CrashListActivity.kt
new file mode 100644
index 0000000000..136e12cabe
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CrashListActivity.kt
@@ -0,0 +1,24 @@
+/* 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.focus.activity
+
+import android.content.Intent
+import androidx.core.net.toUri
+import mozilla.components.lib.crash.CrashReporter
+import mozilla.components.lib.crash.ui.AbstractCrashListActivity
+import org.mozilla.focus.ext.components
+
+class CrashListActivity : AbstractCrashListActivity() {
+ override val crashReporter: CrashReporter by lazy { components.crashReporter }
+
+ override fun onCrashServiceSelected(url: String) {
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ data = url.toUri()
+ `package` = packageName
+ }
+ startActivity(intent)
+ finish()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CustomTabActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CustomTabActivity.kt
new file mode 100644
index 0000000000..df2ae98de2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/CustomTabActivity.kt
@@ -0,0 +1,95 @@
+/* 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.focus.activity
+
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.View
+import mozilla.components.browser.state.selector.findCustomTab
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.support.locale.LocaleAwareAppCompatActivity
+import mozilla.components.support.utils.SafeIntent
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.updateSecureWindowFlags
+import org.mozilla.focus.fragment.BrowserFragment
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupTypeTelemetry
+
+/**
+ * The main entry point for "custom tabs" opened by third-party apps.
+ */
+class CustomTabActivity : LocaleAwareAppCompatActivity() {
+ private lateinit var customTabId: String
+ private lateinit var browserFragment: BrowserFragment
+
+ private val startupPathProvider = StartupPathProvider()
+ private lateinit var startupTypeTelemetry: StartupTypeTelemetry
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ updateSecureWindowFlags()
+ super.onCreate(savedInstanceState)
+
+ val intent = SafeIntent(intent)
+ val customTabId = intent.getStringExtra(CUSTOM_TAB_ID)
+
+ // The session for this ID, no longer exists. This usually happens because we were gc-d
+ // and since we do not save custom tab sessions, the activity is re-created and we no longer
+ // have a session with us to restore. It's safer to finish the activity instead.
+ if (customTabId == null || components.store.state.findCustomTab(customTabId) == null) {
+ finish()
+ return
+ }
+
+ this.customTabId = customTabId
+
+ @Suppress("DEPRECATION") // https://github.com/mozilla-mobile/focus-android/issues/5016
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+
+ setContentView(R.layout.activity_customtab)
+
+ if (savedInstanceState == null || !this::browserFragment.isInitialized) {
+ browserFragment = BrowserFragment.createForTab(customTabId)
+ supportFragmentManager.beginTransaction()
+ .add(R.id.container, browserFragment)
+ .commit()
+ }
+
+ startupPathProvider.attachOnActivityOnCreate(lifecycle, intent.unsafe)
+ startupTypeTelemetry = StartupTypeTelemetry(components.startupStateProvider, startupPathProvider).apply {
+ attachOnMainActivityOnCreate(lifecycle)
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ if (isFinishing) {
+ components.customTabsUseCases.remove(customTabId)
+ }
+ }
+
+ override fun onBackPressed() {
+ if (browserFragment.sessionFeature.onBackPressed()) {
+ return
+ } else {
+ onBackPressedDispatcher.onBackPressed()
+ }
+ }
+
+ override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
+ return if (name == EngineView::class.java.name) {
+ val engineView = components.engine.createView(context, attrs)
+ engineView.asView()
+ } else {
+ super.onCreateView(parent, name, context, attrs)
+ }
+ }
+
+ companion object {
+ const val CUSTOM_TAB_ID = "custom_tab_id"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseAndOpenShortcutActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseAndOpenShortcutActivity.kt
new file mode 100644
index 0000000000..47a914553a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseAndOpenShortcutActivity.kt
@@ -0,0 +1,31 @@
+/* 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.focus.activity
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import mozilla.components.browser.state.selector.privateTabs
+import org.mozilla.focus.GleanMetrics.AppShortcuts
+import org.mozilla.focus.ext.components
+
+class EraseAndOpenShortcutActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ components.tabsUseCases.removeAllTabs()
+
+ val tabCount = components.store.state.privateTabs.size
+ AppShortcuts.eraseOpenButtonTapped.record(AppShortcuts.EraseOpenButtonTappedExtra(tabCount))
+
+ val intent = Intent(this, MainActivity::class.java)
+ intent.action = MainActivity.ACTION_OPEN
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ startActivity(intent)
+
+ finish()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseShortcutActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseShortcutActivity.kt
new file mode 100644
index 0000000000..4f7f160911
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/EraseShortcutActivity.kt
@@ -0,0 +1,23 @@
+/* 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.focus.activity
+
+import android.app.Activity
+import android.os.Bundle
+import mozilla.components.browser.state.selector.privateTabs
+import org.mozilla.focus.GleanMetrics.AppShortcuts
+import org.mozilla.focus.ext.components
+
+class EraseShortcutActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ components.tabsUseCases.removeAllTabs()
+
+ val tabCount = components.store.state.privateTabs.size
+ AppShortcuts.justEraseButtonTapped.record(AppShortcuts.JustEraseButtonTappedExtra(tabCount))
+
+ finishAndRemoveTask()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/InstallFirefoxActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/InstallFirefoxActivity.kt
new file mode 100644
index 0000000000..5e85af2670
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/InstallFirefoxActivity.kt
@@ -0,0 +1,93 @@
+/* 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.focus.activity
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.webkit.WebView
+import androidx.core.net.toUri
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.utils.Browsers
+import mozilla.components.support.utils.ext.resolveActivityCompat
+import org.mozilla.focus.GleanMetrics.OpenWith
+import org.mozilla.focus.utils.AppConstants
+
+/**
+ * Helper activity that will open the Google Play store by following a redirect URL.
+ */
+class InstallFirefoxActivity : Activity() {
+
+ private var webView: WebView? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ webView = WebView(this)
+
+ setContentView(webView)
+
+ webView!!.loadUrl(REDIRECT_URL)
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ if (webView != null) {
+ webView!!.onPause()
+ }
+
+ finish()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ if (webView != null) {
+ webView!!.destroy()
+ }
+ }
+
+ companion object {
+ private const val REDIRECT_URL = "https://app.adjust.com/gs1ao4"
+
+ fun resolveAppStore(context: Context): ActivityInfo? {
+ val resolveInfo = context.packageManager.resolveActivityCompat(createStoreIntent(), 0)
+
+ if (resolveInfo?.activityInfo == null) {
+ return null
+ }
+
+ return if (!resolveInfo.activityInfo.exported) {
+ // We are not allowed to launch this activity.
+ null
+ } else {
+ resolveInfo.activityInfo
+ }
+ }
+
+ private fun createStoreIntent(): Intent {
+ return Intent(
+ Intent.ACTION_VIEW,
+ ("market://details?id=" + Browsers.KnownBrowser.FIREFOX.packageName).toUri(),
+ )
+ }
+
+ fun open(context: Context) {
+ if (AppConstants.isKlarBuild) {
+ // Redirect to Google Play directly
+ context.startActivity(createStoreIntent())
+ } else {
+ // Start this activity to load the redirect URL in a WebView.
+ val intent = Intent(context, InstallFirefoxActivity::class.java)
+ context.startActivity(intent)
+ }
+
+ OpenWith.installFirefox.record(NoExtras())
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/IntentReceiverActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/IntentReceiverActivity.kt
new file mode 100644
index 0000000000..027ade16bc
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/IntentReceiverActivity.kt
@@ -0,0 +1,72 @@
+/* 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.focus.activity
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import mozilla.components.feature.intent.ext.sanitize
+import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
+import mozilla.components.support.utils.toSafeIntent
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.session.IntentProcessor
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * This activity receives VIEW intents and either forwards them to MainActivity or CustomTabActivity.
+ */
+class IntentReceiverActivity : Activity() {
+ private val intentProcessor by lazy {
+ IntentProcessor(this, components.tabsUseCases, components.customTabsUseCases)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val intent = intent.sanitize().toSafeIntent()
+
+ if (intent.dataString.equals(SupportUtils.OPEN_WITH_DEFAULT_BROWSER_URL)) {
+ dispatchNormalIntent()
+ return
+ }
+
+ val result = intentProcessor.handleIntent(this, intent, savedInstanceState)
+ if (result is IntentProcessor.Result.CustomTab) {
+ dispatchCustomTabsIntent(result.id)
+ } else {
+ dispatchNormalIntent()
+ }
+
+ finish()
+ }
+
+ private fun dispatchCustomTabsIntent(tabId: String) {
+ val intent = Intent(intent)
+
+ intent.setClassName(applicationContext, CustomTabActivity::class.java.name)
+
+ // We are adding a generated custom tab ID to the intent here. CustomTabActivity will
+ // use this ID to later decide what session to display once it is created.
+ intent.putExtra(CustomTabActivity.CUSTOM_TAB_ID, tabId)
+
+ startActivity(intent)
+ }
+
+ private fun dispatchNormalIntent() {
+ val intent = Intent(intent)
+ intent.setClassName(applicationContext, MainActivity::class.java.name)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ intent.putExtra(SEARCH_WIDGET_EXTRA, intent.getBooleanExtra(SEARCH_WIDGET_EXTRA, false))
+ intent.putExtra(
+ BaseVoiceSearchActivity.SPEECH_PROCESSING,
+ intent.getStringExtra(BaseVoiceSearchActivity.SPEECH_PROCESSING),
+ )
+ startActivity(intent)
+ }
+
+ companion object {
+ const val SEARCH_WIDGET_EXTRA = "search_widget_extra"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/MainActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/MainActivity.kt
new file mode 100644
index 0000000000..f81e0fb926
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/MainActivity.kt
@@ -0,0 +1,473 @@
+/* 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.focus.activity
+
+import android.Manifest.permission.POST_NOTIFICATIONS
+import android.content.Context
+import android.content.Intent
+import android.content.res.Configuration
+import android.graphics.Color
+import android.os.Build
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.ActionBar
+import androidx.appcompat.widget.Toolbar
+import androidx.core.content.ContextCompat
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.core.view.isVisible
+import androidx.preference.PreferenceManager
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
+import mozilla.components.lib.auth.canUseBiometricFeature
+import mozilla.components.lib.crash.Crash
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.base.feature.UserInteractionHandler
+import mozilla.components.support.ktx.android.content.getColorFromAttr
+import mozilla.components.support.ktx.android.view.createWindowInsetsController
+import mozilla.components.support.locale.LocaleAwareAppCompatActivity
+import mozilla.components.support.utils.SafeIntent
+import mozilla.components.support.utils.StatusBarUtils
+import org.mozilla.experiments.nimbus.initializeTooling
+import org.mozilla.focus.GleanMetrics.AppOpened
+import org.mozilla.focus.GleanMetrics.Notifications
+import org.mozilla.focus.R
+import org.mozilla.focus.appreview.AppReviewUtils
+import org.mozilla.focus.databinding.ActivityMainBinding
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.setNavigationIcon
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.updateSecureWindowFlags
+import org.mozilla.focus.fragment.BrowserFragment
+import org.mozilla.focus.fragment.UrlInputFragment
+import org.mozilla.focus.navigation.MainActivityNavigation
+import org.mozilla.focus.navigation.Navigator
+import org.mozilla.focus.searchwidget.ExternalIntentNavigation
+import org.mozilla.focus.session.IntentProcessor
+import org.mozilla.focus.session.PrivateNotificationFeature
+import org.mozilla.focus.shortcut.HomeScreen
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupTypeTelemetry
+import org.mozilla.focus.utils.SupportUtils
+
+private const val REQUEST_TIME_OUT = 2000L
+
+@Suppress("TooManyFunctions", "LargeClass")
+open class MainActivity : LocaleAwareAppCompatActivity() {
+ private var isToolbarInflated = false
+ private val intentProcessor by lazy {
+ IntentProcessor(this, components.tabsUseCases, components.customTabsUseCases)
+ }
+
+ private val navigator by lazy { Navigator(components.appStore, MainActivityNavigation(this)) }
+ private val tabCount: Int
+ get() = components.store.state.privateTabs.size
+
+ private val startupPathProvider = StartupPathProvider()
+ private lateinit var startupTypeTelemetry: StartupTypeTelemetry
+ private var _binding: ActivityMainBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var privateNotificationFeature: PrivateNotificationFeature
+ private val notificationPermission =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
+ when {
+ granted -> {
+ privateNotificationFeature.start()
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ components.experiments.initializeTooling(applicationContext, intent)
+ installSplashScreen()
+
+ updateSecureWindowFlags()
+
+ super.onCreate(savedInstanceState)
+ _binding = ActivityMainBinding.inflate(layoutInflater)
+ // Checks if Activity is currently in PiP mode if launched from external intents, then exits it
+ checkAndExitPiP()
+
+ if (!isTaskRoot) {
+ if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN == intent.action) {
+ finish()
+ return
+ }
+ }
+
+ @Suppress("DEPRECATION") // https://github.com/mozilla-mobile/focus-android/issues/5016
+ window.decorView.systemUiVisibility =
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+
+ window.statusBarColor = ContextCompat.getColor(this, android.R.color.transparent)
+ when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
+ Configuration.UI_MODE_NIGHT_UNDEFINED, // We assume light here per Android doc's recommendation
+ Configuration.UI_MODE_NIGHT_NO,
+ -> {
+ updateLightSystemBars()
+ }
+ Configuration.UI_MODE_NIGHT_YES -> {
+ clearLightSystemBars()
+ }
+ }
+ setContentView(binding.root)
+
+ startupPathProvider.attachOnActivityOnCreate(lifecycle, intent)
+ startupTypeTelemetry = StartupTypeTelemetry(components.startupStateProvider, startupPathProvider).apply {
+ attachOnMainActivityOnCreate(lifecycle)
+ }
+
+ val safeIntent = SafeIntent(intent)
+
+ lifecycle.addObserver(navigator)
+
+ if (savedInstanceState == null) {
+ handleAppNavigation(safeIntent)
+ }
+
+ if (savedInstanceState == null && intent.hasExtra(HomeScreen.ADD_TO_HOMESCREEN_TAG)) {
+ intentProcessor.handleNewIntent(this, safeIntent)
+ }
+
+ if (safeIntent.isLauncherIntent) {
+ AppOpened.fromIcons.record(AppOpened.FromIconsExtra(AppOpenType.LAUNCH.type))
+ }
+
+ val launchCount = settings.getAppLaunchCount()
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .edit()
+ .putInt(getString(R.string.app_launch_count), launchCount + 1)
+ .apply()
+
+ AppReviewUtils.showAppReview(this)
+
+ privateNotificationFeature = PrivateNotificationFeature(
+ context = applicationContext,
+ browserStore = components.store,
+ permissionRequestHandler = { requestNotificationPermission() },
+ ).also {
+ it.start()
+ }
+
+ components.notificationsDelegate.bindToActivity(this)
+ }
+
+ private fun requestNotificationPermission() {
+ privateNotificationFeature.stop()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ notificationPermission.launch(POST_NOTIFICATIONS)
+ }
+ }
+
+ private fun setSplashScreenPreDrawListener(safeIntent: SafeIntent) {
+ val endTime = System.currentTimeMillis() + REQUEST_TIME_OUT
+ binding.container.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ return if (System.currentTimeMillis() >= endTime) {
+ ExternalIntentNavigation.handleAppNavigation(
+ bundle = safeIntent.extras,
+ context = this@MainActivity,
+ )
+ binding.container.viewTreeObserver.removeOnPreDrawListener(this)
+ true
+ } else {
+ false
+ }
+ }
+ },
+ )
+ }
+
+ private fun checkAndExitPiP() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode && intent != null) {
+ // Exit PiP mode
+ moveTaskToBack(false)
+ startActivity(Intent(this, this::class.java).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT))
+ }
+ }
+
+ final override fun onUserLeaveHint() {
+ // The notification permission prompt will trigger onUserLeaveHint too.
+ // We shouldn't treat this situation as user leaving.
+ if (!components.notificationsDelegate.isRequestingPermission) {
+ val browserFragment =
+ supportFragmentManager.findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
+ if (browserFragment is UserInteractionHandler && browserFragment.onHomePressed()) {
+ return
+ }
+ }
+ super.onUserLeaveHint()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ checkBiometricStillValid()
+ }
+
+ override fun onPause() {
+ val fragmentManager = supportFragmentManager
+ val browserFragment =
+ fragmentManager.findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
+ browserFragment?.cancelAnimation()
+
+ val urlInputFragment =
+ fragmentManager.findFragmentByTag(UrlInputFragment.FRAGMENT_TAG) as UrlInputFragment?
+ urlInputFragment?.cancelAnimation()
+
+ super.onPause()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ }
+
+ override fun onNewIntent(unsafeIntent: Intent) {
+ if (Crash.isCrashIntent(unsafeIntent)) {
+ val browserFragment = supportFragmentManager
+ .findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
+ val crash = Crash.fromIntent(unsafeIntent)
+
+ browserFragment?.handleTabCrash(crash)
+ }
+ startupPathProvider.onIntentReceived(intent)
+ val intent = SafeIntent(unsafeIntent)
+
+ handleAppRestoreFromBackground(intent)
+
+ if (intent.dataString.equals(SupportUtils.OPEN_WITH_DEFAULT_BROWSER_URL)) {
+ components.appStore.dispatch(
+ AppAction.OpenSettings(
+ page = Screen.Settings.Page.General,
+ ),
+ )
+ super.onNewIntent(unsafeIntent)
+ return
+ }
+
+ val action = intent.action
+
+ if (intent.hasExtra(HomeScreen.ADD_TO_HOMESCREEN_TAG)) {
+ intentProcessor.handleNewIntent(this, intent)
+ }
+
+ if (ACTION_OPEN == action) {
+ Notifications.openButtonTapped.record(NoExtras())
+ }
+
+ if (ACTION_ERASE == action) {
+ processEraseAction(intent)
+ }
+
+ if (intent.isLauncherIntent) {
+ AppOpened.fromIcons.record(AppOpened.FromIconsExtra(AppOpenType.RESUME.type))
+ }
+
+ super.onNewIntent(unsafeIntent)
+ }
+
+ private fun handleAppRestoreFromBackground(intent: SafeIntent) {
+ if (!intent.extras?.getString(BaseVoiceSearchActivity.SPEECH_PROCESSING).isNullOrEmpty()) {
+ handleAppNavigation(intent)
+ return
+ }
+ when (components.appStore.state.screen) {
+ is Screen.Settings -> components.appStore.dispatch(
+ AppAction.OpenSettings(
+ page =
+ (components.appStore.state.screen as Screen.Settings).page,
+ ),
+ )
+ is Screen.SitePermissionOptionsScreen -> components.appStore.dispatch(
+ AppAction.OpenSitePermissionOptionsScreen(
+ sitePermission =
+ (components.appStore.state.screen as Screen.SitePermissionOptionsScreen).sitePermission,
+ ),
+ )
+ else -> {
+ handleAppNavigation(intent)
+ }
+ }
+ }
+
+ private fun handleAppNavigation(intent: SafeIntent) {
+ if (components.appStore.state.screen == Screen.Locked()) {
+ components.appStore.dispatch(AppAction.Lock(intent.extras))
+ } else if (settings.getAppLaunchCount() == 0) {
+ setSplashScreenPreDrawListener(intent)
+ } else {
+ ExternalIntentNavigation.handleAppNavigation(
+ bundle = intent.extras,
+ context = this,
+ )
+ }
+ }
+
+ private fun processEraseAction(intent: SafeIntent) {
+ val fromNotificationAction = intent.getBooleanExtra(EXTRA_NOTIFICATION, false)
+
+ components.tabsUseCases.removeAllTabs()
+
+ if (fromNotificationAction) {
+ Notifications.eraseOpenButtonTapped.record(Notifications.EraseOpenButtonTappedExtra(tabCount))
+ }
+ }
+
+ override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
+ return if (name == EngineView::class.java.name) {
+ components.engine.createView(context, attrs).asView()
+ } else {
+ super.onCreateView(parent, name, context, attrs)
+ }
+ }
+
+ override fun onBackPressed() {
+ val fragmentManager = supportFragmentManager
+
+ val urlInputFragment =
+ fragmentManager.findFragmentByTag(UrlInputFragment.FRAGMENT_TAG) as UrlInputFragment?
+ if (urlInputFragment != null &&
+ urlInputFragment.isVisible &&
+ urlInputFragment.onBackPressed()
+ ) {
+ // The URL input fragment has handled the back press. It does its own animations so
+ // we do not try to remove it from outside.
+ return
+ }
+
+ val browserFragment =
+ fragmentManager.findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
+ if (browserFragment != null &&
+ browserFragment.isVisible &&
+ browserFragment.onBackPressed()
+ ) {
+ // The Browser fragment handles back presses on its own because it might just go back
+ // in the browsing history.
+ return
+ }
+
+ val appStore = components.appStore
+ if (appStore.state.screen is Screen.Settings || appStore.state.screen is Screen.SitePermissionOptionsScreen) {
+ // When on a settings screen we want the same behavior as navigating "up" via the toolbar
+ // and therefore dispatch the `NavigateUp` action on the app store.
+ val selectedTabId = components.store.state.selectedTabId
+ appStore.dispatch(AppAction.NavigateUp(selectedTabId))
+ return
+ }
+
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ // We forward an up action to the app store with the NavigateUp action to let the reducer
+ // decide to show a different screen.
+ val selectedTabId = components.store.state.selectedTabId
+ components.appStore.dispatch(AppAction.NavigateUp(selectedTabId))
+ return true
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ // Handles the edge case of a user removing all enrolled prints while auth was enabled
+ private fun checkBiometricStillValid() {
+ // Disable biometrics if the user is no longer eligible due to un-enrolling fingerprints:
+ if (!canUseBiometricFeature()) {
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .edit().putBoolean(
+ getString(R.string.pref_key_biometric),
+ false,
+ ).apply()
+ }
+ }
+
+ private fun updateLightSystemBars() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.statusBarColor = getColorFromAttr(android.R.attr.statusBarColor)
+ window.createWindowInsetsController().isAppearanceLightStatusBars = true
+ } else {
+ window.statusBarColor = Color.BLACK
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // API level can display handle light navigation bar color
+ window.createWindowInsetsController().isAppearanceLightNavigationBars = true
+ window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ window.navigationBarDividerColor =
+ ContextCompat.getColor(this, android.R.color.transparent)
+ }
+ }
+ }
+
+ private fun clearLightSystemBars() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ window.createWindowInsetsController().isAppearanceLightStatusBars = false
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // API level can display handle light navigation bar color
+ window.createWindowInsetsController().isAppearanceLightNavigationBars = false
+ }
+ }
+
+ fun getToolbar(): ActionBar {
+ if (!isToolbarInflated) {
+ val toolbar = binding.toolbar.inflate() as Toolbar
+ setSupportActionBar(toolbar)
+ setNavigationIcon(R.drawable.ic_back_button)
+ isToolbarInflated = true
+ }
+ return supportActionBar!!
+ }
+
+ fun customizeStatusBar(backgroundColorId: Int? = null) {
+ with(binding.statusBarBackground) {
+ binding.statusBarBackground.isVisible = true
+ StatusBarUtils.getStatusBarHeight(this) { statusBarHeight ->
+ layoutParams.height = statusBarHeight
+ backgroundColorId?.let { color ->
+ setBackgroundColor(ContextCompat.getColor(context, color))
+ }
+ }
+ }
+ }
+
+ fun hideStatusBarBackground() {
+ binding.statusBarBackground.isVisible = false
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ _binding = null
+
+ if (this::privateNotificationFeature.isInitialized) {
+ privateNotificationFeature.stop()
+ }
+
+ components.notificationsDelegate.unBindActivity(this)
+ }
+
+ enum class AppOpenType(val type: String) {
+ LAUNCH("Launch"),
+ RESUME("Resume"),
+ }
+
+ companion object {
+ const val ACTION_ERASE = "erase"
+ const val ACTION_OPEN = "open"
+
+ const val EXTRA_NOTIFICATION = "notification"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/TextActionActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/TextActionActivity.kt
new file mode 100644
index 0000000000..2181ded3ae
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/activity/TextActionActivity.kt
@@ -0,0 +1,48 @@
+/* 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.focus.activity
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.core.net.toUri
+import mozilla.components.feature.search.ext.buildSearchUrl
+import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine
+import mozilla.components.support.utils.SafeIntent
+import org.mozilla.focus.ext.components
+
+/**
+ * Activity for receiving and processing an ACTION_PROCESS_TEXT intent.
+ */
+class TextActionActivity : Activity() {
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val intent = SafeIntent(intent)
+
+ val searchTextCharSequence = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
+ val searchText = searchTextCharSequence?.toString() ?: ""
+
+ components.store.waitForSelectedOrDefaultSearchEngine { searchEngine ->
+ val searchUrl = searchEngine?.buildSearchUrl(searchText).toString()
+ val searchIntent = Intent(this, IntentReceiverActivity::class.java)
+ searchIntent.action = Intent.ACTION_VIEW
+ searchIntent.putExtra(EXTRA_TEXT_SELECTION, true)
+ searchIntent.data = searchUrl.toUri()
+ searchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+
+ startActivity(searchIntent)
+
+ finish()
+ }
+ }
+
+ companion object {
+ const val EXTRA_TEXT_SELECTION = "text_selection"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/animation/TransitionDrawableGroup.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/animation/TransitionDrawableGroup.kt
new file mode 100644
index 0000000000..3c1e30d44e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/animation/TransitionDrawableGroup.kt
@@ -0,0 +1,26 @@
+/* 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.focus.animation
+
+import android.graphics.drawable.TransitionDrawable
+
+/**
+ * A class to allow [TransitionDrawable]'s animations to play together: similar to [android.animation.AnimatorSet].
+ */
+class TransitionDrawableGroup(private vararg val transitionDrawables: TransitionDrawable) {
+ fun startTransition(durationMillis: Int) {
+ // In theory, there are no guarantees these will play together.
+ // In practice, I haven't noticed any problems.
+ for (transitionDrawable in transitionDrawables) {
+ transitionDrawable.startTransition(durationMillis)
+ }
+ }
+
+ fun resetTransition() {
+ for (transitionDrawable in transitionDrawables) {
+ transitionDrawable.resetTransition()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewStep.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewStep.kt
new file mode 100644
index 0000000000..47089dc410
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewStep.kt
@@ -0,0 +1,11 @@
+/* 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.focus.appreview
+
+enum class AppReviewStep {
+ Pending,
+ ReviewNeeded,
+ Reviewed,
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewUtils.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewUtils.kt
new file mode 100644
index 0000000000..d180bc6bac
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/appreview/AppReviewUtils.kt
@@ -0,0 +1,151 @@
+/* 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.focus.appreview
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import androidx.core.net.toUri
+import androidx.preference.PreferenceManager
+import com.google.android.play.core.review.ReviewInfo
+import com.google.android.play.core.review.ReviewManagerFactory
+import com.google.android.play.core.tasks.Task
+import mozilla.components.browser.state.state.SessionState
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.utils.SupportUtils
+import java.util.concurrent.TimeUnit
+
+class AppReviewUtils {
+ companion object {
+ /**
+ * Number of app openings until In App Review is triggered.
+ */
+ private const val APP_OPENINGS_REVIEW_TRIGGER = 3
+ private val APP_REVIEW_TIME_TRIGGER = TimeUnit.DAYS.toMillis(90)
+
+ /**
+ * Shows in app review or opens play store if Review Info task is not successful.
+ *
+ * @property activity where In App review is triggered
+ */
+ fun showAppReview(activity: Activity) {
+ if (shouldShowInAppReview(activity)) {
+ val manager = ReviewManagerFactory.create(activity)
+ val request = manager.requestReviewFlow()
+ request.addOnCompleteListener { task: Task ->
+ if (task.isSuccessful) {
+ // We can get the ReviewInfo object
+ val reviewInfo = task.result
+ val flow = manager.launchReviewFlow(activity, reviewInfo)
+ flow.addOnCompleteListener {
+ // The flow has finished. The API does not indicate whether the user
+ // reviewed or not, or even whether the review dialog was shown. Thus, no
+ // matter the result, we continue our app flow.
+ setAppReviewed(activity)
+ }
+ } else {
+ setAppReviewed(activity)
+ // There was some problem, open PlayStore
+ openPlayStore(activity = activity)
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the number of app openings and the flag when In App Review is needed.
+ *
+ * @property context needed for SharePref
+ */
+ fun addAppOpenings(context: Context) {
+ val preferenceManage = PreferenceManager.getDefaultSharedPreferences(context)
+ val currentOpeningsNumber = preferenceManage.getInt(
+ context.getString(
+ R.string.pref_in_app_review_openings,
+ ),
+ 0,
+ ) + 1
+ val appReviewStep = preferenceManage.getString(
+ context.getString(
+ R.string.pref_in_app_review_step,
+ ),
+ AppReviewStep.Pending.name,
+ )
+
+ preferenceManage
+ .edit().putInt(
+ context.getString(R.string.pref_in_app_review_openings),
+ currentOpeningsNumber,
+ ).apply()
+ if (currentOpeningsNumber == APP_OPENINGS_REVIEW_TRIGGER &&
+ appReviewStep == AppReviewStep.Pending.name
+ ) {
+ setAppReviewStep(context, AppReviewStep.ReviewNeeded)
+ }
+ }
+
+ private fun shouldShowInAppReview(context: Context): Boolean {
+ val inAppReviewStep = PreferenceManager.getDefaultSharedPreferences(context).getString(
+ context.getString(R.string.pref_in_app_review_step),
+ AppReviewStep.Pending.name,
+ )
+ val lastReviewedTime = PreferenceManager.getDefaultSharedPreferences(context).getLong(
+ context.getString(R.string.pref_in_app_review_time),
+ 0L,
+ )
+
+ return inAppReviewStep == AppReviewStep.ReviewNeeded.name || (
+ lastReviewedTime +
+ APP_REVIEW_TIME_TRIGGER <= System.currentTimeMillis() &&
+ inAppReviewStep == AppReviewStep.Reviewed.name
+ )
+ }
+
+ private fun openPlayStore(activity: Activity) {
+ try {
+ activity.startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ SupportUtils.RATE_APP_URL.toUri(),
+ ),
+ )
+ } catch (e: ActivityNotFoundException) {
+ // Device without the play store installed.
+ // Opening the play store website.
+ val tabId = activity.components.tabsUseCases.addTab(
+ url = SupportUtils.FOCUS_PLAY_STORE_URL,
+ source = SessionState.Source.Internal.NewTab,
+ selectTab = true,
+ private = true,
+ )
+ activity.components.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+ }
+
+ private fun setAppReviewed(activity: Activity) {
+ setAppReviewStep(activity, AppReviewStep.Reviewed)
+ setLastReviewedTime(activity)
+ }
+
+ private fun setAppReviewStep(context: Context, appReviewStep: AppReviewStep) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit().putString(
+ context.getString(R.string.pref_in_app_review_step),
+ appReviewStep.name,
+ ).apply()
+ }
+
+ private fun setLastReviewedTime(context: Context) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit().putLong(
+ context.getString(R.string.pref_in_app_review_time),
+ System.currentTimeMillis(),
+ ).apply()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteAddFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteAddFragment.kt
new file mode 100644
index 0000000000..4da800d468
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteAddFragment.kt
@@ -0,0 +1,120 @@
+/* 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.focus.autocomplete
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import mozilla.components.browser.domains.CustomDomains
+import org.mozilla.focus.GleanMetrics.Autocomplete
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentAutocompleteAddDomainBinding
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsLikeFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.utils.ViewUtils
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Fragment showing settings UI to add custom autocomplete domains.
+ */
+class AutocompleteAddFragment : BaseSettingsLikeFragment(), CoroutineScope {
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Main
+ private var _binding: FragmentAutocompleteAddDomainBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onResume() {
+ super.onResume()
+
+ if (job.isCancelled) {
+ job = Job()
+ }
+
+ showToolbar(getString(R.string.preference_autocomplete_title_add))
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentAutocompleteAddDomainBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ ViewUtils.showKeyboard(binding.domainView)
+ }
+
+ override fun onPause() {
+ job.cancel()
+ ViewUtils.hideKeyboard(activity?.currentFocus)
+ super.onPause()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_autocomplete_add, menu)
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
+ R.id.save -> {
+ val domain = binding.domainView.text.toString().trim()
+
+ launch(IO) {
+ val domains = CustomDomains.load(requireActivity())
+ val error = when {
+ domain.isEmpty() -> getString(R.string.preference_autocomplete_add_error)
+ domains.contains(domain) -> getString(R.string.preference_autocomplete_duplicate_url_error)
+ else -> null
+ }
+
+ launch(Main) {
+ if (error != null) {
+ binding.domainView.error = error
+ } else {
+ saveDomainAndClose(requireActivity().applicationContext, domain)
+ }
+ }
+ }
+ true
+ }
+ // other options are not handled by this menu provider
+ else -> false
+ }
+
+ private fun saveDomainAndClose(context: Context, domain: String) {
+ launch(IO) {
+ CustomDomains.add(context, domain)
+ Autocomplete.domainAdded.add()
+ }
+
+ ViewUtils.showBrandedSnackbar(view, R.string.preference_autocomplete_add_confirmation, 0)
+
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(
+ requireComponents.store.state.selectedTabId,
+ ),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteCustomDomainsPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteCustomDomainsPreference.kt
new file mode 100644
index 0000000000..3a71b24790
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteCustomDomainsPreference.kt
@@ -0,0 +1,23 @@
+/* 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.focus.autocomplete
+
+import android.content.Context
+import android.util.AttributeSet
+import org.mozilla.focus.settings.LearnMoreSwitchPreference
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * Switch preference for enabling/disabling autocompletion for custom domains entered by the user.
+ */
+class AutocompleteCustomDomainsPreference(
+ context: Context,
+ attrs: AttributeSet?,
+) : LearnMoreSwitchPreference(context, attrs) {
+ override fun getLearnMoreUrl() = SupportUtils.getSumoURLForTopic(
+ SupportUtils.getAppVersion(context),
+ SupportUtils.SumoTopic.AUTOCOMPLETE,
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDefaultDomainsPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDefaultDomainsPreference.kt
new file mode 100644
index 0000000000..1a8509d1f3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDefaultDomainsPreference.kt
@@ -0,0 +1,23 @@
+/* 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.focus.autocomplete
+
+import android.content.Context
+import android.util.AttributeSet
+import org.mozilla.focus.settings.LearnMoreSwitchPreference
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * Switch preference for enabling/disabling autocompletion for default domains that ship with the app.
+ */
+class AutocompleteDefaultDomainsPreference(
+ context: Context,
+ attrs: AttributeSet?,
+) : LearnMoreSwitchPreference(context, attrs) {
+ override fun getLearnMoreUrl() = SupportUtils.getSumoURLForTopic(
+ SupportUtils.getAppVersion(context),
+ SupportUtils.SumoTopic.AUTOCOMPLETE,
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDomainFormatter.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDomainFormatter.kt
new file mode 100644
index 0000000000..d62b3a8b35
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteDomainFormatter.kt
@@ -0,0 +1,18 @@
+/* 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.focus.autocomplete
+
+object AutocompleteDomainFormatter {
+ private const val HOST_INDEX = 3
+ private val urlMatcher = Regex("""(https?://)?(www.)?(.+)?""")
+
+ fun format(url: String): String {
+ val result = urlMatcher.find(url)
+
+ return result?.let {
+ it.groups[HOST_INDEX]?.value
+ } ?: url
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteListFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteListFragment.kt
new file mode 100644
index 0000000000..7d2f793746
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteListFragment.kt
@@ -0,0 +1,349 @@
+/* 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.focus.autocomplete
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.CompoundButton
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import mozilla.components.browser.domains.CustomDomains
+import org.mozilla.focus.GleanMetrics.Autocomplete
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentAutocompleteCustomdomainsBinding
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsLikeFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.utils.ViewUtils
+import java.util.Collections
+import kotlin.coroutines.CoroutineContext
+
+typealias DomainFormatter = (String) -> String
+
+/**
+ * Fragment showing settings UI listing all custom autocomplete domains entered by the user.
+ */
+open class AutocompleteListFragment : BaseSettingsLikeFragment(), CoroutineScope {
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Main
+ private var _binding: FragmentAutocompleteCustomdomainsBinding? = null
+ protected val binding get() = _binding!!
+
+ /**
+ * ItemTouchHelper for reordering items in the domain list.
+ */
+ val itemTouchHelper: ItemTouchHelper = ItemTouchHelper(
+ object : SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
+ override fun onMove(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder,
+ ): Boolean {
+ val from = viewHolder.bindingAdapterPosition
+ val to = target.bindingAdapterPosition
+
+ (recyclerView.adapter as DomainListAdapter).move(from, to)
+
+ return true
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
+
+ override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
+ if (viewHolder is AddActionViewHolder) {
+ return ItemTouchHelper.Callback.makeMovementFlags(0, 0)
+ }
+
+ return super.getMovementFlags(recyclerView, viewHolder)
+ }
+
+ override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
+ super.onSelectedChanged(viewHolder, actionState)
+
+ if (viewHolder is DomainViewHolder) {
+ viewHolder.onSelected()
+ }
+ }
+
+ override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
+ super.clearView(recyclerView, viewHolder)
+
+ if (viewHolder is DomainViewHolder) {
+ viewHolder.onCleared()
+ }
+ }
+
+ override fun canDropOver(
+ recyclerView: RecyclerView,
+ current: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder,
+ ): Boolean {
+ if (target is AddActionViewHolder) {
+ return false
+ }
+
+ return super.canDropOver(recyclerView, current, target)
+ }
+ },
+ )
+
+ /**
+ * In selection mode the user can select and remove items. In non-selection mode the list can
+ * be reordered by the user.
+ */
+ open fun isSelectionMode() = false
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentAutocompleteCustomdomainsBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.domainList.apply {
+ layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
+ adapter = DomainListAdapter()
+ setHasFixedSize(true)
+
+ if (!isSelectionMode()) {
+ itemTouchHelper.attachToRecyclerView(this)
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ if (job.isCancelled) {
+ job = Job()
+ }
+
+ showToolbar(getString(R.string.preference_autocomplete_subitem_manage_sites))
+
+ (binding.domainList.adapter as DomainListAdapter).refresh(requireActivity()) {
+ activity?.invalidateOptionsMenu()
+ }
+ }
+
+ override fun onPause() {
+ job.cancel()
+ super.onPause()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_autocomplete_list, menu)
+ }
+
+ override fun onPrepareMenu(menu: Menu) {
+ val removeItem = menu.findItem(R.id.remove)
+
+ removeItem?.let {
+ it.isVisible = isSelectionMode() || binding.domainList.adapter!!.itemCount > 1
+ val isEnabled =
+ !isSelectionMode() || (binding.domainList.adapter as DomainListAdapter).selection()
+ .isNotEmpty()
+ ViewUtils.setMenuItemEnabled(it, isEnabled)
+ }
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
+ R.id.remove -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SearchAutocompleteRemove),
+ )
+ true
+ }
+ else -> false
+ }
+
+ /**
+ * Adapter implementation for the list of custom autocomplete domains.
+ */
+ inner class DomainListAdapter : RecyclerView.Adapter() {
+ private val domains: MutableList = mutableListOf()
+ private val selectedDomains: MutableList = mutableListOf()
+
+ fun refresh(context: Context, body: (() -> Unit)? = null) {
+ launch(Main) {
+ val updatedDomains =
+ withContext(Dispatchers.Default) {
+ CustomDomains.load(context)
+ }
+
+ domains.clear()
+ domains.addAll(updatedDomains)
+
+ notifyDataSetChanged()
+
+ body?.invoke()
+ }
+ }
+
+ override fun getItemViewType(position: Int) =
+ when (position) {
+ domains.size -> AddActionViewHolder.LAYOUT_ID
+ else -> DomainViewHolder.LAYOUT_ID
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
+ when (viewType) {
+ AddActionViewHolder.LAYOUT_ID ->
+ AddActionViewHolder(
+ this@AutocompleteListFragment,
+ LayoutInflater.from(parent.context).inflate(viewType, parent, false),
+ )
+ DomainViewHolder.LAYOUT_ID ->
+ DomainViewHolder(
+ LayoutInflater.from(parent.context).inflate(viewType, parent, false),
+ ) { AutocompleteDomainFormatter.format(it) }
+ else -> throw IllegalArgumentException("Unknown view type: $viewType")
+ }
+
+ override fun getItemCount(): Int = domains.size + if (isSelectionMode()) 0 else 1
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ if (holder is DomainViewHolder) {
+ holder.bind(
+ domains[position],
+ isSelectionMode(),
+ selectedDomains,
+ itemTouchHelper,
+ this@AutocompleteListFragment,
+ )
+ }
+ }
+
+ override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
+ if (holder is DomainViewHolder) {
+ holder.checkBoxView.setOnCheckedChangeListener(null)
+ }
+ }
+
+ fun selection(): List = selectedDomains
+
+ fun move(from: Int, to: Int) {
+ Collections.swap(domains, from, to)
+ notifyItemMoved(from, to)
+
+ launch(IO) {
+ CustomDomains.save(activity!!.applicationContext, domains)
+ Autocomplete.listOrderChanged.add()
+ }
+ }
+ }
+
+ /**
+ * ViewHolder implementation for a domain item in the list.
+ */
+ private class DomainViewHolder(
+ itemView: View,
+ val domainFormatter: DomainFormatter? = null,
+ ) : RecyclerView.ViewHolder(itemView) {
+ val domainView: TextView = itemView.findViewById(R.id.domainView)
+ val checkBoxView: CheckBox = itemView.findViewById(R.id.checkbox)
+ val handleView: View = itemView.findViewById(R.id.handleView)
+
+ companion object {
+ val LAYOUT_ID = R.layout.item_custom_domain
+ }
+
+ fun bind(
+ domain: String,
+ isSelectionMode: Boolean,
+ selectedDomains: MutableList,
+ itemTouchHelper: ItemTouchHelper,
+ fragment: AutocompleteListFragment,
+ ) {
+ domainView.text = domainFormatter?.invoke(domain) ?: domain
+
+ checkBoxView.isVisible = isSelectionMode
+ checkBoxView.isChecked = selectedDomains.contains(domain)
+ checkBoxView.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
+ if (isChecked) {
+ selectedDomains.add(domain)
+ } else {
+ selectedDomains.remove(domain)
+ }
+
+ fragment.activity?.invalidateOptionsMenu()
+ }
+
+ handleView.isVisible = !isSelectionMode
+ handleView.setOnTouchListener { _, event ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ itemTouchHelper.startDrag(this)
+ }
+ false
+ }
+
+ if (isSelectionMode) {
+ itemView.setOnClickListener {
+ checkBoxView.isChecked = !checkBoxView.isChecked
+ }
+ }
+ }
+
+ fun onSelected() {
+ itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.disabled))
+ }
+
+ fun onCleared() {
+ itemView.setBackgroundColor(0)
+ }
+ }
+
+ /**
+ * ViewHolder implementation for a "Add custom domain" item at the bottom of the list.
+ */
+ private class AddActionViewHolder(
+ val fragment: AutocompleteListFragment,
+ itemView: View,
+ ) : RecyclerView.ViewHolder(itemView) {
+ init {
+ itemView.setOnClickListener {
+ fragment.requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SearchAutocompleteAdd),
+ )
+ }
+ }
+
+ companion object {
+ val LAYOUT_ID = R.layout.item_add_custom_domain
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteRemoveFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteRemoveFragment.kt
new file mode 100644
index 0000000000..248a514ed7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteRemoveFragment.kt
@@ -0,0 +1,74 @@
+/* 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.focus.autocomplete
+
+import android.content.Context
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import mozilla.components.browser.domains.CustomDomains
+import org.mozilla.focus.GleanMetrics.Autocomplete
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.state.AppAction
+import kotlin.coroutines.CoroutineContext
+
+class AutocompleteRemoveFragment : AutocompleteListFragment(), CoroutineScope {
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Main
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_autocomplete_remove, menu)
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
+ R.id.remove -> {
+ removeSelectedDomains(requireActivity().applicationContext)
+ true
+ }
+ else -> false
+ }
+
+ private fun removeSelectedDomains(context: Context) {
+ val domains = (binding.domainList.adapter as DomainListAdapter).selection()
+ if (domains.isNotEmpty()) {
+ launch(Main) {
+ withContext(Dispatchers.Default) {
+ CustomDomains.remove(context, domains)
+ Autocomplete.domainRemoved.add()
+ }
+
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(requireComponents.store.state.selectedTabId),
+ )
+ }
+ }
+ }
+
+ override fun isSelectionMode() = true
+
+ override fun onResume() {
+ super.onResume()
+
+ if (job.isCancelled) {
+ job = Job()
+ }
+
+ showToolbar(getString(R.string.preference_autocomplete_title_remove))
+ }
+
+ override fun onPause() {
+ job.cancel()
+ super.onPause()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteSettingsFragment.kt
new file mode 100644
index 0000000000..58c4eb13a8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/autocomplete/AutocompleteSettingsFragment.kt
@@ -0,0 +1,84 @@
+/* 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.focus.autocomplete
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.preference.Preference
+import org.mozilla.focus.GleanMetrics.Autocomplete
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.requirePreference
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+
+/**
+ * Settings UI for configuring autocomplete.
+ */
+class AutocompleteSettingsFragment : BaseSettingsFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private lateinit var topSitesAutocomplete: AutocompleteDefaultDomainsPreference
+ private lateinit var favoriteSitesAutocomplete: AutocompleteCustomDomainsPreference
+
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.autocomplete)
+ val appName = requireContext().getString(R.string.app_name)
+
+ topSitesAutocomplete =
+ requirePreference(R.string.pref_key_autocomplete_preinstalled).apply {
+ summary =
+ context.getString(R.string.preference_autocomplete_topsite_summary2, appName)
+ }
+ favoriteSitesAutocomplete =
+ requirePreference(R.string.pref_key_autocomplete_custom).apply {
+ summary =
+ context.getString(R.string.preference_autocomplete_user_list_summary2, appName)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.preference_subitem_autocomplete))
+
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ if (preference.key == getString(R.string.pref_key_screen_custom_domains)) {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SearchAutocompleteList),
+ )
+ }
+
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ if (key == null || sharedPreferences == null) {
+ return
+ }
+
+ when (key) {
+ topSitesAutocomplete.key ->
+ Autocomplete.topSitesSettingChanged.record(
+ Autocomplete.TopSitesSettingChangedExtra(sharedPreferences.all[key] as Boolean),
+ )
+
+ favoriteSitesAutocomplete.key ->
+ Autocomplete.favoriteSitesSettingChanged.record(
+ Autocomplete.FavoriteSitesSettingChangedExtra(sharedPreferences.all[key] as Boolean),
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragment.kt
new file mode 100644
index 0000000000..8f86e53ee0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragment.kt
@@ -0,0 +1,131 @@
+/* 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.focus.biometrics
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import mozilla.components.lib.auth.AuthenticationDelegate
+import mozilla.components.lib.auth.BiometricPromptAuth
+import mozilla.components.lib.auth.canUseBiometricFeature
+import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.hideToolbar
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.searchwidget.ExternalIntentNavigation
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.ui.theme.FocusTheme
+
+/**
+ * Fragment used to display biometric authentication when the app is locked.
+ */
+class BiometricAuthenticationFragment : Fragment(), AuthenticationDelegate {
+ @VisibleForTesting
+ internal val biometricPromptAuth = ViewBoundFeatureWrapper()
+
+ @VisibleForTesting
+ internal val biometricErrorText = mutableStateOf("")
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setBiometricPrompt(this)
+ setContent {
+ FocusTheme {
+ val biometricErrorText by biometricErrorText
+ BiometricPromptContent(biometricErrorText) {
+ showBiometricPrompt(
+ biometricPromptAuth.get(),
+ getString(R.string.biometric_prompt_title),
+ getString(R.string.biometric_prompt_subtitle),
+ )
+ }
+ }
+ }
+ isTransitionGroup = true
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ hideToolbar()
+ }
+ override fun onAuthError(errorText: String) {
+ biometricErrorText.value = errorText
+ }
+
+ override fun onAuthFailure() {
+ // onAuthFailure
+ }
+
+ override fun onAuthSuccess() {
+ onAuthenticated()
+ }
+
+ @VisibleForTesting
+ internal fun showBiometricPrompt(
+ biometricPromptAuth: BiometricPromptAuth?,
+ title: String,
+ subtitle: String,
+ ) {
+ if (context?.canUseBiometricFeature() == true) {
+ biometricPromptAuth?.requestAuthentication(
+ title = title,
+ subtitle = subtitle,
+ )
+ }
+ }
+
+ private fun setBiometricPrompt(view: View) {
+ biometricPromptAuth.set(
+ feature = BiometricPromptAuth(
+ context = requireContext(),
+ fragment = this,
+ authenticationDelegate = this,
+ ),
+ owner = this,
+ view = view,
+ )
+ }
+
+ @VisibleForTesting
+ internal fun onAuthenticated() {
+ ExternalIntentNavigation.handleAppNavigation(
+ bundle = arguments,
+ context = requireContext(),
+ )
+
+ val tabId = requireComponents.store.state.selectedTabId
+ requireComponents.appStore.dispatch(AppAction.Unlock(tabId))
+ dismiss()
+ }
+
+ @VisibleForTesting
+ internal fun dismiss() {
+ requireActivity().supportFragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
+ }
+
+ companion object {
+ const val FRAGMENT_TAG = "biometric-authentication-fragment"
+
+ /**
+ * Creates a [BiometricAuthenticationFragment] with redirection to a destination from @param [bundle].
+ */
+ fun createWithDestinationData(bundle: Bundle? = null): BiometricAuthenticationFragment {
+ val fragment = BiometricAuthenticationFragment()
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt
new file mode 100644
index 0000000000..378e4b7150
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt
@@ -0,0 +1,114 @@
+/* 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.focus.biometrics
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusTypography
+
+@Composable
+@Preview
+private fun BiometricPromptContentPreview() {
+ FocusTheme {
+ BiometricPromptContent("Fingerprint operation canceled by user.") {}
+ }
+}
+
+/**
+ * Content of the biometric authentication prompt.
+ * @param biometricErrorText Text for an authentication error
+ * @param showBiometricPrompt callback for displaying the OS biometric authentication prompt
+ */
+@Composable
+fun BiometricPromptContent(biometricErrorText: String, showBiometricPrompt: () -> Unit) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .background(
+ brush = Brush.linearGradient(
+ colors = listOf(
+ colorResource(R.color.home_screen_modal_gradient_one),
+ colorResource(R.color.home_screen_modal_gradient_two),
+ colorResource(R.color.home_screen_modal_gradient_three),
+ colorResource(R.color.home_screen_modal_gradient_four),
+ colorResource(R.color.home_screen_modal_gradient_five),
+ colorResource(R.color.home_screen_modal_gradient_six),
+ ),
+ end = Offset(0f, Float.POSITIVE_INFINITY),
+ start = Offset(Float.POSITIVE_INFINITY, 0f),
+ ),
+ ),
+ ) {
+ Image(
+ painter = painterResource(R.drawable.wordmark2),
+ contentDescription = LocalContext.current.getString(R.string.app_name),
+ modifier = Modifier
+ .padding(start = 24.dp, end = 24.dp),
+ )
+ Text(
+ style = focusTypography.onboardingButton,
+ color = Color.Red,
+ text = biometricErrorText,
+ modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),
+ )
+ ComponentShowBiometricPromptButton {
+ showBiometricPrompt()
+ }
+ }
+}
+
+@Composable
+private fun ComponentShowBiometricPromptButton(showBiometricPrompt: () -> Unit) {
+ Button(
+ onClick = showBiometricPrompt,
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = colorResource(R.color.biometric_show_button_background),
+ ),
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ ) {
+ Image(
+ painter = painterResource(R.drawable.ic_fingerprint),
+ contentDescription = LocalContext.current.getString(R.string.biometric_auth_image_description),
+ modifier = Modifier
+ .padding(end = 10.dp),
+ )
+ Text(
+ color = PhotonColors.White,
+ text = AnnotatedString(
+ LocalContext.current.resources.getString(
+ R.string.show_biometric_button_text,
+ ),
+ ),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/LockObserver.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/LockObserver.kt
new file mode 100644
index 0000000000..7625effa9c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/LockObserver.kt
@@ -0,0 +1,59 @@
+/* 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.focus.biometrics
+
+import android.content.Context
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.lib.auth.canUseBiometricFeature
+import org.mozilla.focus.GleanMetrics.TabCount
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+import org.mozilla.focus.topsites.DefaultTopSitesStorage
+
+class LockObserver(
+ private val context: Context,
+ private val browserStore: BrowserStore,
+ private val appStore: AppStore,
+) : DefaultLifecycleObserver {
+
+ override fun onCreate(owner: LifecycleOwner) {
+ super.onCreate(owner)
+ triggerAppLock()
+ }
+
+ override fun onPause(owner: LifecycleOwner) {
+ super.onPause(owner)
+ triggerAppLock()
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ private fun triggerAppLock() {
+ GlobalScope.launch(Dispatchers.IO) {
+ val tabCount = browserStore.state.privateTabs.size.toLong()
+ TabCount.appBackgrounded.accumulateSamples(listOf(tabCount))
+ val topSitesList = context.components.topSitesStorage.getTopSites(
+ totalSites = DefaultTopSitesStorage.TOP_SITES_MAX_LIMIT,
+ frecencyConfig = null,
+ )
+ if (tabCount == 0L && topSitesList.isEmpty()) {
+ return@launch
+ }
+ if (context.settings.shouldUseBiometrics() &&
+ context.canUseBiometricFeature()
+ ) {
+ appStore.dispatch(AppAction.Lock())
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/BlockedTrackersMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/BlockedTrackersMiddleware.kt
new file mode 100644
index 0000000000..4fc2de851f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/BlockedTrackersMiddleware.kt
@@ -0,0 +1,55 @@
+/* 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.focus.browser
+
+import android.content.Context
+import androidx.preference.PreferenceManager
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.TrackingProtectionAction
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.settings
+
+/**
+ * [Middleware] to record the number of blocked trackers in response to [BrowserAction]s.
+ * @param context The application context.
+ */
+class BlockedTrackersMiddleware(
+ private val context: Context,
+) : Middleware {
+
+ private val settings = context.settings
+ private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ when (action) {
+ is TrackingProtectionAction.TrackerBlockedAction -> {
+ incrementCount()
+ }
+ else -> {
+ // no-op
+ }
+ }
+
+ next(action)
+ }
+
+ private fun incrementCount() {
+ val blockedTrackersCount = settings.getTotalBlockedTrackersCount()
+ preferences
+ .edit()
+ .putInt(
+ context.getString(R.string.pref_key_privacy_total_trackers_blocked_count),
+ blockedTrackersCount + 1,
+ )
+ .apply()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/LocalizedContent.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/LocalizedContent.kt
new file mode 100644
index 0000000000..304fe4b281
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/LocalizedContent.kt
@@ -0,0 +1,106 @@
+/* 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.focus.browser
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.view.View
+import androidx.collection.ArrayMap
+import mozilla.components.support.utils.ext.getPackageInfoCompat
+import org.mozilla.focus.R
+import org.mozilla.focus.locale.Locales
+import org.mozilla.focus.utils.HtmlLoader
+import org.mozilla.focus.utils.SupportUtils.manifestoURL
+import org.mozilla.geckoview.BuildConfig
+import java.util.Locale
+
+object LocalizedContent {
+ // We can't use "about:" because webview silently swallows about: pages, hence we use
+ // a custom scheme.
+ const val URL_ABOUT = "focus:about"
+ const val URL_RIGHTS = "focus:rights"
+ const val URL_GPL = "focus:gpl"
+ const val URL_LICENSES = "focus:licenses"
+
+ /**
+ * Load the content for focus:about
+ */
+ fun loadAbout(context: Context): String {
+ val resources = Locales.getLocalizedResources(context)
+ val substitutionMap: MutableMap = ArrayMap()
+ val appName = context.resources.getString(R.string.app_name)
+ val learnMoreURL = manifestoURL
+ var aboutVersion = ""
+ try {
+ val engineIndicator = " \uD83E\uDD8E " + BuildConfig.MOZ_APP_VERSION + "-" +
+ BuildConfig.MOZ_APP_BUILDID
+ val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
+ @Suppress("DEPRECATION")
+ aboutVersion = String.format(
+ Locale.US,
+ "%s (Build #%s)",
+ packageInfo.versionName,
+ packageInfo.versionCode.toString() + engineIndicator,
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Nothing to do if we can't find the package name.
+ }
+ substitutionMap["%about-version%"] = aboutVersion
+ val aboutContent = resources.getString(R.string.about_content, appName, learnMoreURL)
+ substitutionMap["%about-content%"] = aboutContent
+ val wordmark = HtmlLoader.loadPngAsDataURI(context, R.drawable.wordmark2)
+ substitutionMap["%wordmark%"] = wordmark
+ putLayoutDirectionIntoMap(substitutionMap, context)
+ return HtmlLoader.loadResourceFile(context, R.raw.about, substitutionMap)
+ }
+
+ /**
+ * Load the content for focus:rights
+ */
+ fun loadRights(context: Context): String {
+ val resources = Locales.getLocalizedResources(context)
+ val substitutionMap: MutableMap = ArrayMap()
+ val appName = context.resources.getString(R.string.app_name)
+ val mplUrl = "https://www.mozilla.org/en-US/MPL/"
+ val trademarkPolicyUrl = "https://www.mozilla.org/foundation/trademarks/policy/"
+ val gplUrl = "focus:gpl"
+ val trackingProtectionUrl = "https://wiki.mozilla.org/Security/Tracking_protection#Lists"
+ val licensesUrl = "focus:licenses"
+ val content1 = resources.getString(R.string.your_rights_content1, appName)
+ substitutionMap["%your-rights-content1%"] = content1
+ val content2 = resources.getString(R.string.your_rights_content2, appName, mplUrl)
+ substitutionMap["%your-rights-content2%"] = content2
+ val content3 = resources.getString(R.string.your_rights_content3, appName, trademarkPolicyUrl)
+ substitutionMap["%your-rights-content3%"] = content3
+ val content4 = resources.getString(R.string.your_rights_content4, appName, licensesUrl)
+ substitutionMap["%your-rights-content4%"] = content4
+ val content5 = resources.getString(R.string.your_rights_content5, appName, gplUrl, trackingProtectionUrl)
+ substitutionMap["%your-rights-content5%"] = content5
+ putLayoutDirectionIntoMap(substitutionMap, context)
+ return HtmlLoader.loadResourceFile(context, R.raw.rights, substitutionMap)
+ }
+
+ fun loadLicenses(context: Context): String {
+ return HtmlLoader.loadResourceFile(context, R.raw.licenses, emptyMap())
+ }
+
+ fun loadGPL(context: Context): String {
+ return HtmlLoader.loadResourceFile(context, R.raw.gpl, emptyMap())
+ }
+
+ private fun putLayoutDirectionIntoMap(substitutionMap: MutableMap, context: Context) {
+ val direction: String = when (context.resources.configuration.layoutDirection) {
+ View.LAYOUT_DIRECTION_LTR -> {
+ "ltr"
+ }
+ View.LAYOUT_DIRECTION_RTL -> {
+ "rtl"
+ }
+ else -> {
+ "auto"
+ }
+ }
+ substitutionMap["%dir%"] = direction
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserMenuController.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserMenuController.kt
new file mode 100644
index 0000000000..7deb9d9c99
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserMenuController.kt
@@ -0,0 +1,190 @@
+/* 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.focus.browser.integration
+
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.top.sites.TopSitesUseCases
+import org.mozilla.focus.GleanMetrics.BrowserMenu
+import org.mozilla.focus.GleanMetrics.CustomTabsToolbar
+import org.mozilla.focus.GleanMetrics.Shortcuts
+import org.mozilla.focus.ext.titleOrDomain
+import org.mozilla.focus.menu.ToolbarMenu
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+import org.mozilla.focus.state.Screen
+
+@Suppress("LongParameterList")
+class BrowserMenuController(
+ private val sessionUseCases: SessionUseCases,
+ private val appStore: AppStore,
+ private val store: BrowserStore,
+ private val topSitesUseCases: TopSitesUseCases,
+ private val currentTabId: String,
+ private val shareCallback: () -> Unit,
+ private val requestDesktopCallback: (isChecked: Boolean) -> Unit,
+ private val addToHomeScreenCallback: () -> Unit,
+ private val showFindInPageCallback: () -> Unit,
+ private val openInCallback: () -> Unit,
+ private val openInBrowser: () -> Unit,
+ private val showShortcutAddedSnackBar: () -> Unit,
+) {
+ @VisibleForTesting
+ private val currentTab: SessionState?
+ get() = store.state.findTabOrCustomTabOrSelectedTab(currentTabId)
+
+ @VisibleForTesting
+ internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
+
+ @Suppress("ComplexMethod")
+ fun handleMenuInteraction(item: ToolbarMenu.Item) {
+ recordBrowserMenuTelemetry(item)
+
+ when (item) {
+ is ToolbarMenu.Item.Back, ToolbarMenu.CustomTabItem.Back -> sessionUseCases.goBack(
+ currentTabId,
+ )
+ is ToolbarMenu.Item.Forward, ToolbarMenu.CustomTabItem.Forward -> sessionUseCases.goForward(
+ currentTabId,
+ )
+ is ToolbarMenu.Item.Reload, ToolbarMenu.CustomTabItem.Reload -> {
+ sessionUseCases.reload(currentTabId)
+ }
+ is ToolbarMenu.Item.Stop, ToolbarMenu.CustomTabItem.Stop -> sessionUseCases.stopLoading(
+ currentTabId,
+ )
+ is ToolbarMenu.Item.Share -> shareCallback()
+ is ToolbarMenu.Item.FindInPage, ToolbarMenu.CustomTabItem.FindInPage -> showFindInPageCallback()
+ is ToolbarMenu.Item.AddToShortcuts -> {
+ ioScope.launch {
+ currentTab?.let { state ->
+ topSitesUseCases.addPinnedSites(
+ title = state.content.titleOrDomain,
+ url = state.content.url,
+ )
+ }
+ }
+ showShortcutAddedSnackBar()
+ }
+ is ToolbarMenu.Item.RemoveFromShortcuts -> {
+ ioScope.launch {
+ currentTab?.let { state ->
+ appStore.state.topSites.find { it.url == state.content.url }
+ ?.let { topSite ->
+ topSitesUseCases.removeTopSites(topSite)
+ }
+ }
+ }
+ }
+ is ToolbarMenu.Item.RequestDesktop -> requestDesktopCallback(item.isChecked)
+ is ToolbarMenu.CustomTabItem.RequestDesktop -> requestDesktopCallback(item.isChecked)
+ is ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.CustomTabItem.AddToHomeScreen -> addToHomeScreenCallback()
+ is ToolbarMenu.CustomTabItem.OpenInBrowser -> openInBrowser()
+ is ToolbarMenu.Item.OpenInApp, ToolbarMenu.CustomTabItem.OpenInApp -> openInCallback()
+ is ToolbarMenu.Item.Settings -> appStore.dispatch(AppAction.OpenSettings(page = Screen.Settings.Page.Start))
+ }
+ }
+
+ @Suppress("LongMethod")
+ @VisibleForTesting
+ internal fun recordBrowserMenuTelemetry(item: ToolbarMenu.Item) {
+ when (item) {
+ is ToolbarMenu.Item.Back -> BrowserMenu.navigationToolbarAction.record(
+ BrowserMenu.NavigationToolbarActionExtra("back"),
+ )
+ is ToolbarMenu.Item.Forward -> BrowserMenu.navigationToolbarAction.record(
+ BrowserMenu.NavigationToolbarActionExtra("forward"),
+ )
+ is ToolbarMenu.Item.Reload -> {
+ BrowserMenu.navigationToolbarAction.record(
+ BrowserMenu.NavigationToolbarActionExtra("reload"),
+ )
+ }
+ is ToolbarMenu.Item.Stop -> BrowserMenu.navigationToolbarAction.record(
+ BrowserMenu.NavigationToolbarActionExtra("stop"),
+ )
+ is ToolbarMenu.Item.Share -> BrowserMenu.navigationToolbarAction.record(
+ BrowserMenu.NavigationToolbarActionExtra("share"),
+ )
+ is ToolbarMenu.Item.FindInPage -> BrowserMenu.browserMenuAction.record(
+ BrowserMenu.BrowserMenuActionExtra("find_in_page"),
+ )
+ is ToolbarMenu.Item.AddToShortcuts ->
+ Shortcuts.shortcutAddedCounter.add()
+ is ToolbarMenu.Item.RemoveFromShortcuts ->
+ Shortcuts.shortcutRemovedCounter["removed_from_browser_menu"].add()
+
+ is ToolbarMenu.Item.RequestDesktop -> {
+ if (item.isChecked) {
+ BrowserMenu.browserMenuAction.record(
+ BrowserMenu.BrowserMenuActionExtra("desktop_view_on"),
+ )
+ } else {
+ BrowserMenu.browserMenuAction.record(
+ BrowserMenu.BrowserMenuActionExtra("desktop_view_off"),
+ )
+ }
+ }
+ is ToolbarMenu.Item.AddToHomeScreen -> BrowserMenu.browserMenuAction.record(
+ BrowserMenu.BrowserMenuActionExtra("add_to_home_screen"),
+ )
+
+ is ToolbarMenu.Item.OpenInApp -> BrowserMenu.browserMenuAction.record(
+ BrowserMenu.BrowserMenuActionExtra("open_in_app"),
+ )
+ is ToolbarMenu.Item.Settings -> BrowserMenu.browserMenuAction.record(
+ BrowserMenu.BrowserMenuActionExtra("settings"),
+ )
+
+ // custom tabs
+ ToolbarMenu.CustomTabItem.Back -> CustomTabsToolbar.navigationToolbarAction.record(
+ CustomTabsToolbar.NavigationToolbarActionExtra("back"),
+ )
+ ToolbarMenu.CustomTabItem.Forward -> CustomTabsToolbar.navigationToolbarAction.record(
+ CustomTabsToolbar.NavigationToolbarActionExtra("forward"),
+ )
+ ToolbarMenu.CustomTabItem.Stop -> CustomTabsToolbar.navigationToolbarAction.record(
+ CustomTabsToolbar.NavigationToolbarActionExtra("stop"),
+ )
+
+ ToolbarMenu.CustomTabItem.Reload -> {
+ CustomTabsToolbar.navigationToolbarAction.record(
+ CustomTabsToolbar.NavigationToolbarActionExtra("reload"),
+ )
+ }
+
+ ToolbarMenu.CustomTabItem.AddToHomeScreen -> CustomTabsToolbar.browserMenuAction.record(
+ CustomTabsToolbar.BrowserMenuActionExtra("add_to_home_screen"),
+ )
+ ToolbarMenu.CustomTabItem.OpenInApp -> CustomTabsToolbar.browserMenuAction.record(
+ CustomTabsToolbar.BrowserMenuActionExtra("open_in_app"),
+ )
+ ToolbarMenu.CustomTabItem.OpenInBrowser -> CustomTabsToolbar.browserMenuAction.record(
+ CustomTabsToolbar.BrowserMenuActionExtra("open_in_browser"),
+ )
+
+ ToolbarMenu.CustomTabItem.FindInPage -> CustomTabsToolbar.browserMenuAction.record(
+ CustomTabsToolbar.BrowserMenuActionExtra("find_in_page"),
+ )
+ is ToolbarMenu.CustomTabItem.RequestDesktop -> {
+ if (item.isChecked) {
+ CustomTabsToolbar.browserMenuAction.record(
+ CustomTabsToolbar.BrowserMenuActionExtra("desktop_view_on"),
+ )
+ } else {
+ CustomTabsToolbar.browserMenuAction.record(
+ CustomTabsToolbar.BrowserMenuActionExtra("desktop_view_off"),
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt
new file mode 100644
index 0000000000..e3f8db2f89
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt
@@ -0,0 +1,501 @@
+/* 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.focus.browser.integration
+
+import android.graphics.Color
+import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.appcompat.widget.AppCompatEditText
+import androidx.compose.material.Text
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.children
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.mapNotNull
+import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.browser.toolbar.display.DisplayToolbar.Indicators
+import mozilla.components.compose.cfr.CFRPopup
+import mozilla.components.compose.cfr.CFRPopupProperties
+import mozilla.components.feature.customtabs.CustomTabsToolbarFeature
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.tabs.CustomTabsUseCases
+import mozilla.components.feature.tabs.toolbar.TabCounterToolbarButton
+import mozilla.components.feature.toolbar.ToolbarBehaviorController
+import mozilla.components.feature.toolbar.ToolbarPresenter
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.android.view.hideKeyboard
+import org.mozilla.focus.GleanMetrics.TabCount
+import org.mozilla.focus.GleanMetrics.TrackingProtection
+import org.mozilla.focus.R
+import org.mozilla.focus.cookiebanner.CookieBannerOption
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.isCustomTab
+import org.mozilla.focus.ext.isTablet
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.fragment.BrowserFragment
+import org.mozilla.focus.menu.browser.CustomTabMenu
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.ui.theme.focusTypography
+import org.mozilla.focus.utils.ClickableSubstringLink
+
+@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
+class BrowserToolbarIntegration(
+ private val store: BrowserStore,
+ private val toolbar: BrowserToolbar,
+ private val fragment: BrowserFragment,
+ controller: BrowserMenuController,
+ sessionUseCases: SessionUseCases,
+ customTabsUseCases: CustomTabsUseCases,
+ private val onUrlLongClicked: () -> Boolean,
+ private val eraseActionListener: () -> Unit,
+ private val tabCounterListener: () -> Unit,
+ private val customTabId: String? = null,
+ inTesting: Boolean = false,
+) : LifecycleAwareFeature {
+ private val presenter = ToolbarPresenter(
+ toolbar,
+ store,
+ customTabId,
+ )
+
+ @VisibleForTesting
+ internal var securityIndicatorScope: CoroutineScope? = null
+
+ @VisibleForTesting
+ internal var eraseTabsCfrScope: CoroutineScope? = null
+
+ @VisibleForTesting
+ internal var trackingProtectionCfrScope: CoroutineScope? = null
+
+ @VisibleForTesting
+ internal var cookieBannerCfrScope: CoroutineScope? = null
+
+ private var tabsCounterScope: CoroutineScope? = null
+ private var customTabsFeature: CustomTabsToolbarFeature? = null
+ private var navigationButtonsIntegration: NavigationButtonsIntegration? = null
+ private val eraseAction = BrowserToolbar.Button(
+ imageDrawable = AppCompatResources.getDrawable(
+ toolbar.context,
+ R.drawable.mozac_ic_delete_24,
+ )!!,
+ contentDescription = toolbar.context.getString(R.string.content_description_erase),
+ iconTintColorResource = R.color.primaryText,
+ listener = {
+ val openedTabs = store.state.tabs.size
+ TabCount.eraseButtonTapped.record(TabCount.EraseButtonTappedExtra(openedTabs))
+
+ eraseActionListener.invoke()
+ },
+ )
+ private val tabsAction = TabCounterToolbarButton(
+ lifecycleOwner = fragment,
+ showTabs = {
+ toolbar.hideKeyboard()
+ tabCounterListener.invoke()
+ },
+ store = store,
+ )
+
+ @VisibleForTesting
+ internal var toolbarController = ToolbarBehaviorController(toolbar, store, customTabId)
+
+ init {
+ val context = toolbar.context
+
+ toolbar.display.apply {
+ colors = colors.copy(
+ hint = ContextCompat.getColor(toolbar.context, R.color.urlBarHintText),
+ securityIconInsecure = Color.TRANSPARENT,
+ text = ContextCompat.getColor(toolbar.context, R.color.primaryText),
+ menu = ContextCompat.getColor(toolbar.context, R.color.primaryText),
+ )
+
+ addTrackingProtectionIndicator()
+
+ displayIndicatorSeparator = false
+
+ setOnSiteSecurityClickedListener {
+ TrackingProtection.toolbarShieldClicked.add()
+ fragment.initCookieBanner()
+ fragment.showTrackingProtectionPanel()
+ }
+
+ onUrlClicked = {
+ fragment.edit()
+ false // Do not switch to edit mode
+ }
+
+ setOnUrlLongClickListener { onUrlLongClicked() }
+
+ icons = icons.copy(
+ trackingProtectionTrackersBlocked = AppCompatResources.getDrawable(
+ context,
+ R.drawable.mozac_ic_shield_24,
+ )!!,
+ trackingProtectionNothingBlocked = AppCompatResources.getDrawable(
+ context,
+ R.drawable.mozac_ic_shield_24,
+ )!!,
+ trackingProtectionException = AppCompatResources.getDrawable(
+ context,
+ R.drawable.mozac_ic_shield_slash_24,
+ )!!,
+ )
+ }
+
+ toolbar.display.setOnTrackingProtectionClickedListener {
+ TrackingProtection.toolbarShieldClicked.add()
+ fragment.initCookieBanner()
+ fragment.showTrackingProtectionPanel()
+ }
+
+ if (customTabId != null) {
+ val menu = CustomTabMenu(
+ context = fragment.requireContext(),
+ store = store,
+ currentTabId = customTabId,
+ onItemTapped = { controller.handleMenuInteraction(it) },
+ )
+ customTabsFeature = CustomTabsToolbarFeature(
+ store,
+ toolbar,
+ sessionId = customTabId,
+ useCases = customTabsUseCases,
+ menuBuilder = menu.menuBuilder,
+ window = fragment.activity?.window,
+ menuItemIndex = menu.menuBuilder.items.size - 1,
+ closeListener = { fragment.closeCustomTab() },
+ updateTheme = true,
+ forceActionButtonTinting = false,
+ )
+ }
+
+ val isCustomTab = store.state.findCustomTabOrSelectedTab(customTabId)?.isCustomTab()
+
+ if (context.isTablet() && isCustomTab == false) {
+ navigationButtonsIntegration = NavigationButtonsIntegration(
+ context,
+ store,
+ toolbar,
+ sessionUseCases,
+ customTabId,
+ )
+ }
+
+ if (isCustomTab == false) {
+ toolbar.addNavigationAction(eraseAction)
+ if (!inTesting) {
+ setUrlBackground()
+ }
+ }
+ }
+
+ // Use the same background for display/edit modes.
+ private fun setUrlBackground() {
+ val urlBackground = ResourcesCompat.getDrawable(
+ fragment.resources,
+ R.drawable.toolbar_url_background,
+ fragment.context?.theme,
+ )
+ toolbar.display.setUrlBackground(urlBackground)
+ }
+
+ private fun setBrowserActionButtons() {
+ tabsCounterScope = store.flowScoped { flow ->
+ flow.distinctUntilChangedBy { state -> state.tabs.size > 1 }
+ .collect { state ->
+ if (state.tabs.size > 1) {
+ toolbar.addBrowserAction(tabsAction)
+ } else {
+ toolbar.removeBrowserAction(tabsAction)
+ }
+ }
+ }
+ }
+
+ override fun start() {
+ presenter.start()
+ toolbarController.start()
+ customTabsFeature?.start()
+ navigationButtonsIntegration?.start()
+ observerSecurityIndicatorChanges()
+ if (store.state.findCustomTabOrSelectedTab(customTabId)?.isCustomTab() == false) {
+ setBrowserActionButtons()
+ observeEraseCfr()
+ }
+
+ if (fragment.requireContext().settings.shouldShowCookieBannerCfr &&
+ fragment.requireContext().settings.isCookieBannerEnable &&
+ fragment.requireContext().settings.getCurrentCookieBannerOptionFromSharePref() ==
+ CookieBannerOption.CookieBannerRejectAll()
+ ) {
+ observeCookieBannerCfr()
+ }
+
+ observeTrackingProtectionCfr()
+ }
+
+ @VisibleForTesting
+ internal fun observeEraseCfr() {
+ eraseTabsCfrScope = fragment.components?.appStore?.flowScoped { flow ->
+ flow.mapNotNull { state -> state.showEraseTabsCfr }
+ .distinctUntilChanged()
+ .collect { showEraseCfr ->
+ if (showEraseCfr) {
+ val eraseActionView =
+ toolbar.findViewById(R.id.mozac_browser_toolbar_navigation_actions)
+ .children
+ .last()
+ CFRPopup(
+ anchor = eraseActionView,
+ properties = CFRPopupProperties(
+ popupWidth = 256.dp,
+ popupAlignment = CFRPopup.PopupAlignment.INDICATOR_CENTERED_IN_ANCHOR,
+ popupBodyColors = listOf(
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_end_color,
+ ),
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_start_color,
+ ),
+ ),
+ dismissButtonColor = ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cardview_light_background,
+ ),
+ popupVerticalOffset = 0.dp,
+ ),
+ onDismiss = { onDismissEraseTabsCfr() },
+ text = {
+ Text(
+ style = focusTypography.cfrTextStyle,
+ text = fragment.getString(R.string.cfr_for_toolbar_delete_icon2),
+ color = colorResource(R.color.cfr_text_color),
+ )
+ },
+ ).apply {
+ show()
+ }
+ }
+ }
+ }
+ }
+
+ private fun onDismissEraseTabsCfr() {
+ fragment.components?.appStore?.dispatch(AppAction.ShowEraseTabsCfrChange(false))
+ }
+
+ @VisibleForTesting
+ internal fun observeCookieBannerCfr() {
+ cookieBannerCfrScope = fragment.components?.appStore?.flowScoped { flow ->
+ flow.mapNotNull { state -> state.showCookieBannerCfr }
+ .distinctUntilChanged()
+ .collect { showCookieBannerCfr ->
+ if (showCookieBannerCfr) {
+ CFRPopup(
+ anchor = toolbar.findViewById(R.id.mozac_browser_toolbar_background),
+ properties = CFRPopupProperties(
+ popupWidth = 256.dp,
+ popupAlignment = CFRPopup.PopupAlignment.BODY_TO_ANCHOR_START,
+ popupBodyColors = listOf(
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_end_color,
+ ),
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_start_color,
+ ),
+ ),
+ dismissButtonColor = ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cardview_light_background,
+ ),
+ popupVerticalOffset = 0.dp,
+ indicatorArrowStartOffset = 10.dp,
+ ),
+ onDismiss = { onDismissCookieBannerCfr() },
+ text = {
+ val textCookieBannerCfr = stringResource(
+ id = R.string.cfr_cookie_banner,
+ LocalContext.current.getString(R.string.onboarding_short_app_name),
+ LocalContext.current.getString(R.string.cfr_cookie_banner_link),
+ )
+ ClickableSubstringLink(
+ text = textCookieBannerCfr,
+ style = focusTypography.cfrCookieBannerTextStyle,
+ linkTextDecoration = TextDecoration.Underline,
+ clickableStartIndex = textCookieBannerCfr.indexOf(
+ LocalContext.current.getString(
+ R.string.cfr_cookie_banner_link,
+ ),
+ ),
+ clickableEndIndex = textCookieBannerCfr.length,
+ onClick = {
+ fragment.requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.CookieBanner),
+ )
+ onDismissCookieBannerCfr()
+ },
+ )
+ },
+ ).apply {
+ show()
+ stopObserverCookieBannerCfrChanges()
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun observeTrackingProtectionCfr() {
+ trackingProtectionCfrScope = fragment.components?.appStore?.flowScoped { flow ->
+ flow.mapNotNull { state -> state.showTrackingProtectionCfrForTab }
+ .distinctUntilChanged()
+ .collect { showTrackingProtectionCfrForTab ->
+ if (showTrackingProtectionCfrForTab[store.state.selectedTabId] == true) {
+ CFRPopup(
+ anchor = toolbar.findViewById(
+ R.id.mozac_browser_toolbar_tracking_protection_indicator,
+ ),
+ properties = CFRPopupProperties(
+ popupWidth = 256.dp,
+ popupAlignment = CFRPopup.PopupAlignment.INDICATOR_CENTERED_IN_ANCHOR,
+ popupBodyColors = listOf(
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_end_color,
+ ),
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_start_color,
+ ),
+ ),
+ dismissButtonColor = ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cardview_light_background,
+ ),
+ popupVerticalOffset = 0.dp,
+ ),
+ onDismiss = { onDismissTrackingProtectionCfr() },
+ text = {
+ Text(
+ style = focusTypography.cfrTextStyle,
+ text = fragment.getString(R.string.cfr_for_toolbar_shield_icon2),
+ color = colorResource(R.color.cfr_text_color),
+ )
+ },
+ ).apply {
+ show()
+ }
+ }
+ }
+ }
+ }
+
+ private fun onDismissCookieBannerCfr() {
+ fragment.components?.appStore?.dispatch(
+ AppAction.ShowCookieBannerCfrChange(
+ false,
+ ),
+ )
+ fragment.requireContext().settings.shouldShowCookieBannerCfr = false
+ }
+
+ private fun onDismissTrackingProtectionCfr() {
+ store.state.selectedTabId?.let {
+ fragment.components?.appStore?.dispatch(
+ AppAction.ShowTrackingProtectionCfrChange(
+ mapOf(
+ it to false,
+ ),
+ ),
+ )
+ }
+ fragment.requireContext().settings.shouldShowCfrForTrackingProtection = false
+ FocusNimbus.features.onboarding.recordExposure()
+ fragment.components?.appStore?.dispatch(AppAction.ShowEraseTabsCfrChange(true))
+ }
+
+ @VisibleForTesting
+ internal fun observerSecurityIndicatorChanges() {
+ securityIndicatorScope = store.flowScoped { flow ->
+ flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(customTabId) }
+ .distinctUntilChangedBy { tab -> tab.content.securityInfo }
+ .collect {
+ val secure = it.content.securityInfo.secure
+ val url = it.content.url
+ if (secure && Indicators.SECURITY in toolbar.display.indicators) {
+ addTrackingProtectionIndicator()
+ } else if (!secure && Indicators.SECURITY !in toolbar.display.indicators &&
+ !url.trim().startsWith("about:")
+ ) {
+ addSecurityIndicator()
+ }
+ }
+ }
+ }
+
+ override fun stop() {
+ presenter.stop()
+ toolbarController.stop()
+ customTabsFeature?.stop()
+ navigationButtonsIntegration?.stop()
+ stopObserverSecurityIndicatorChanges()
+ toolbar.removeBrowserAction(tabsAction)
+ tabsCounterScope?.cancel()
+ stopObserverEraseTabsCfrChanges()
+ stopObserverTrackingProtectionCfrChanges()
+ stopObserverCookieBannerCfrChanges()
+ }
+
+ @VisibleForTesting
+ internal fun stopObserverTrackingProtectionCfrChanges() {
+ trackingProtectionCfrScope?.cancel()
+ }
+
+ @VisibleForTesting
+ internal fun stopObserverEraseTabsCfrChanges() {
+ eraseTabsCfrScope?.cancel()
+ }
+
+ @VisibleForTesting
+ internal fun stopObserverSecurityIndicatorChanges() {
+ securityIndicatorScope?.cancel()
+ }
+
+ @VisibleForTesting
+ internal fun stopObserverCookieBannerCfrChanges() {
+ cookieBannerCfrScope?.cancel()
+ }
+
+ @VisibleForTesting
+ internal fun addSecurityIndicator() {
+ toolbar.display.indicators = listOf(Indicators.SECURITY)
+ }
+
+ @VisibleForTesting
+ internal fun addTrackingProtectionIndicator() {
+ toolbar.display.indicators = listOf(Indicators.TRACKING_PROTECTION)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FindInPageIntegration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FindInPageIntegration.kt
new file mode 100644
index 0000000000..18742f2994
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FindInPageIntegration.kt
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.focus.browser.integration
+
+import androidx.core.view.isVisible
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.feature.findinpage.FindInPageFeature
+import mozilla.components.feature.findinpage.view.FindInPageBar
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.base.feature.UserInteractionHandler
+
+class FindInPageIntegration(
+ store: BrowserStore,
+ private val findInPageView: FindInPageBar,
+ private val browserToolbar: BrowserToolbar,
+ engineView: EngineView,
+) : LifecycleAwareFeature, UserInteractionHandler {
+ private val feature = FindInPageFeature(
+ store,
+ findInPageView,
+ engineView,
+ ::hide,
+ )
+
+ override fun start() {
+ feature.start()
+ }
+
+ override fun stop() {
+ feature.stop()
+ }
+
+ override fun onBackPressed(): Boolean {
+ return feature.onBackPressed()
+ }
+
+ fun show(sessionState: SessionState) {
+ findInPageView.isVisible = true
+ // Hiding the toolbar prevents Talkback from dictating its elements.
+ browserToolbar.isVisible = false
+ feature.bind(sessionState)
+ }
+
+ fun hide() {
+ findInPageView.isVisible = false
+ browserToolbar.isVisible = true
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt
new file mode 100644
index 0000000000..a003636912
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt
@@ -0,0 +1,133 @@
+/* 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.focus.browser.integration
+
+import android.app.Activity
+import android.os.Build
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentManager
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.feature.prompts.dialog.FullScreenNotification
+import mozilla.components.feature.prompts.dialog.FullScreenNotificationDialog
+import mozilla.components.feature.session.FullScreenFeature
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.base.feature.UserInteractionHandler
+import mozilla.components.support.ktx.android.view.enterImmersiveMode
+import mozilla.components.support.ktx.android.view.exitImmersiveMode
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.disableDynamicBehavior
+import org.mozilla.focus.ext.enableDynamicBehavior
+import org.mozilla.focus.ext.hide
+import org.mozilla.focus.ext.showAsFixed
+import org.mozilla.focus.utils.Settings
+
+@Suppress("LongParameterList")
+class FullScreenIntegration(
+ val activity: Activity,
+ val store: BrowserStore,
+ tabId: String?,
+ sessionUseCases: SessionUseCases,
+ private val settings: Settings,
+ private val toolbarView: BrowserToolbar,
+ private val statusBar: View,
+ private val engineView: EngineView,
+ private val parentFragmentManager: FragmentManager,
+) : LifecycleAwareFeature, UserInteractionHandler {
+ @VisibleForTesting
+ internal var feature = FullScreenFeature(
+ store,
+ sessionUseCases,
+ tabId,
+ ::viewportFitChanged,
+ ::fullScreenChanged,
+ )
+
+ override fun start() {
+ feature.start()
+ }
+
+ override fun stop() {
+ feature.stop()
+ }
+
+ @VisibleForTesting
+ internal fun fullScreenChanged(
+ enabled: Boolean,
+ fullScreenNotification: FullScreenNotification =
+ FullScreenNotificationDialog(R.layout.dialog_full_screen_notification),
+ ) {
+ if (enabled) {
+ enterBrowserFullscreen()
+ statusBar.isVisible = false
+
+ fullScreenNotification.show(parentFragmentManager)
+
+ switchToImmersiveMode()
+ } else {
+ // If the video is in PiP, but is not in fullscreen anymore we should move the task containing
+ // this activity to the back of the activity stack
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInPictureInPictureMode) {
+ activity.moveTaskToBack(false)
+ }
+ statusBar.isVisible = true
+ exitBrowserFullscreen()
+
+ exitImmersiveMode()
+ }
+ }
+
+ override fun onBackPressed(): Boolean {
+ return feature.onBackPressed()
+ }
+
+ @VisibleForTesting
+ internal fun viewportFitChanged(viewportFit: Int) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ activity.window.attributes.layoutInDisplayCutoutMode = viewportFit
+ }
+ }
+
+ /**
+ * Hide system bars. They can be revealed temporarily with system gestures, such as swiping from
+ * the top of the screen. These transient system bars will overlay app’s content, may have some
+ * degree of transparency, and will automatically hide after a short timeout.
+ */
+ @VisibleForTesting
+ internal fun switchToImmersiveMode() {
+ activity.enterImmersiveMode()
+ }
+
+ /**
+ * Show the system bars again.
+ */
+ fun exitImmersiveMode() {
+ activity.exitImmersiveMode()
+ }
+
+ @VisibleForTesting
+ internal fun enterBrowserFullscreen() {
+ if (settings.isAccessibilityEnabled()) {
+ toolbarView.hide(engineView)
+ } else {
+ toolbarView.collapse()
+ toolbarView.disableDynamicBehavior(engineView)
+ }
+ }
+
+ @VisibleForTesting
+ internal fun exitBrowserFullscreen() {
+ if (settings.isAccessibilityEnabled()) {
+ toolbarView.showAsFixed(activity, engineView)
+ } else {
+ toolbarView.enableDynamicBehavior(activity, engineView)
+ toolbarView.expand()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/NavigationButtonsIntegration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/NavigationButtonsIntegration.kt
new file mode 100644
index 0000000000..056d8bfb3a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/NavigationButtonsIntegration.kt
@@ -0,0 +1,123 @@
+/* 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.focus.browser.integration
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
+import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
+import mozilla.components.support.utils.ColorUtils
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.ifCustomTab
+import org.mozilla.focus.theme.resolveAttribute
+
+class NavigationButtonsIntegration(
+ val context: Context,
+ val store: BrowserStore,
+ val toolbar: BrowserToolbar,
+ private val sessionUseCases: SessionUseCases,
+ private val customTabId: String?,
+) : LifecycleAwareFeature {
+ private var scope: CoroutineScope? = null
+
+ private var enabledColorRes = context.theme.resolveAttribute(R.attr.primaryText)
+ private var disabledColorRes = context.theme.resolveAttribute(R.attr.disabled)
+
+ init {
+ store.state.findCustomTabOrSelectedTab(customTabId)?.ifCustomTab()?.let { sessionState ->
+ sessionState.config.colorSchemes?.defaultColorSchemeParams?.toolbarColor?.let { color ->
+ if (!ColorUtils.isDark(color)) {
+ enabledColorRes = R.color.enabled_button_tint
+ disabledColorRes = R.color.disabled
+ }
+ }
+ }
+
+ val backButton = BrowserToolbar.TwoStateButton(
+ primaryImage = ContextCompat.getDrawable(context, R.drawable.mozac_ic_back_24)!!,
+ primaryContentDescription = context.getString(R.string.content_description_back),
+ primaryImageTintResource = enabledColorRes,
+ isInPrimaryState = {
+ store.state.findCustomTabOrSelectedTab(customTabId)?.content?.canGoBack
+ ?: false
+ },
+ secondaryImageTintResource = disabledColorRes,
+ disableInSecondaryState = true,
+ longClickListener = null,
+ listener = {
+ sessionUseCases.goBack(store.state.findCustomTabOrSelectedTab(customTabId)?.id)
+ },
+ )
+ toolbar.addNavigationAction(backButton)
+
+ val forwardButton = BrowserToolbar.TwoStateButton(
+ primaryImage = ContextCompat.getDrawable(context, R.drawable.mozac_ic_forward_24)!!,
+ primaryContentDescription = context.getString(R.string.content_description_forward),
+ primaryImageTintResource = enabledColorRes,
+ isInPrimaryState = {
+ store.state.findCustomTabOrSelectedTab(customTabId)?.content?.canGoForward
+ ?: false
+ },
+ secondaryImageTintResource = disabledColorRes,
+ disableInSecondaryState = true,
+ longClickListener = null,
+ listener = {
+ sessionUseCases.goForward(store.state.findCustomTabOrSelectedTab(customTabId)?.id)
+ },
+ )
+ toolbar.addNavigationAction(forwardButton)
+
+ val reloadOrStopButton = BrowserToolbar.TwoStateButton(
+ primaryImage = ContextCompat.getDrawable(context, R.drawable.mozac_ic_stop)!!,
+ secondaryImage = ContextCompat.getDrawable(context, R.drawable.mozac_ic_arrow_clockwise_24)!!,
+ primaryContentDescription = context.getString(R.string.content_description_stop),
+ secondaryContentDescription = context.getString(R.string.content_description_reload),
+ primaryImageTintResource = enabledColorRes,
+ isInPrimaryState = {
+ store.state.findCustomTabOrSelectedTab(customTabId)?.content?.loading ?: false
+ },
+ secondaryImageTintResource = enabledColorRes,
+ disableInSecondaryState = false,
+ longClickListener = null,
+ listener = {
+ val tab = store.state.findCustomTabOrSelectedTab(customTabId)
+ ?: return@TwoStateButton
+ if (tab.content.loading) {
+ sessionUseCases.stopLoading(tab.id)
+ } else {
+ sessionUseCases.reload(tab.id)
+ }
+ },
+ )
+ toolbar.addNavigationAction(reloadOrStopButton)
+ }
+
+ override fun start() {
+ scope = store.flowScoped { flow ->
+ flow.map { state -> state.findCustomTabOrSelectedTab(customTabId) }
+ .ifAnyChanged { tab ->
+ arrayOf(
+ tab?.content?.canGoBack,
+ tab?.content?.canGoForward,
+ tab?.content?.loading,
+ )
+ }
+ .collect { toolbar.invalidateActions() }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cfr/CfrMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cfr/CfrMiddleware.kt
new file mode 100644
index 0000000000..16ea8393f6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cfr/CfrMiddleware.kt
@@ -0,0 +1,123 @@
+/* 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.focus.cfr
+
+import android.content.Context
+import androidx.core.net.toUri
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.action.CookieBannerAction
+import mozilla.components.browser.state.action.TrackingProtectionAction
+import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.CookieBanner
+import org.mozilla.focus.cookiebanner.CookieBannerOption
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.truncatedHost
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.nimbus.Onboarding
+import org.mozilla.focus.state.AppAction
+
+/**
+ * Middleware used to intercept browser store actions in order to decide when should we display a specific CFR
+ */
+class CfrMiddleware(private val appContext: Context) : Middleware {
+ private val onboardingFeature = FocusNimbus.features.onboarding
+ private lateinit var onboardingConfig: Onboarding
+ private val components = appContext.components
+ private var isCurrentTabSecure = false
+ private var tpExposureAlreadyRecorded = false
+
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ onboardingConfig = onboardingFeature.value()
+ if (onboardingConfig.isCfrEnabled) {
+ next(action)
+ showCookieBannerCfr(action)
+ showTrackingProtectionCfr(action, context)
+ } else {
+ next(action)
+ }
+ }
+
+ private fun showCookieBannerCfr(
+ action: BrowserAction,
+ ) {
+ if (action is CookieBannerAction.UpdateStatusAction &&
+ shouldShowCookieBannerCfr(action) &&
+ otherCfrHasBeenShown()
+ ) {
+ CookieBanner.cookieBannerCfrShown.record(NoExtras())
+ components.appStore.dispatch(
+ AppAction.ShowCookieBannerCfrChange(true),
+ )
+ }
+ }
+
+ private fun showTrackingProtectionCfr(
+ action: BrowserAction,
+ context: MiddlewareContext,
+ ) {
+ if (action is ContentAction.UpdateSecurityInfoAction) {
+ isCurrentTabSecure = action.securityInfo.secure
+ }
+ if (shouldShowCfrForTrackingProtection(action = action, browserState = context.state)) {
+ if (!tpExposureAlreadyRecorded) {
+ FocusNimbus.features.onboarding.recordExposure()
+ tpExposureAlreadyRecorded = true
+ }
+
+ components.appStore.dispatch(
+ AppAction.ShowTrackingProtectionCfrChange(
+ mapOf((action as TrackingProtectionAction.TrackerBlockedAction).tabId to true),
+ ),
+ )
+ }
+ }
+
+ private fun isMozillaUrl(browserState: BrowserState): Boolean {
+ return browserState.findTabOrCustomTabOrSelectedTab(
+ browserState.selectedTabId,
+ )?.content?.url?.toUri()?.truncatedHost()?.substringBefore(".") == ("mozilla")
+ }
+
+ private fun isActionSecure(action: BrowserAction) =
+ action is TrackingProtectionAction.TrackerBlockedAction && isCurrentTabSecure
+
+ private fun shouldShowCfrForTrackingProtection(
+ action: BrowserAction,
+ browserState: BrowserState,
+ ) = (
+ isActionSecure(action = action) &&
+ !isMozillaUrl(browserState = browserState) &&
+ components.settings.shouldShowCfrForTrackingProtection &&
+ !components.appStore.state.showEraseTabsCfr
+ )
+
+ private fun otherCfrHasBeenShown(): Boolean {
+ return (
+ !appContext.settings.shouldShowCfrForTrackingProtection &&
+ !components.appStore.state.showEraseTabsCfr
+ )
+ }
+
+ private fun shouldShowCookieBannerCfr(action: CookieBannerAction.UpdateStatusAction): Boolean {
+ return (
+ !appContext.settings.isFirstRun &&
+ appContext.settings.shouldShowCookieBannerCfr &&
+ appContext.settings.isCookieBannerEnable &&
+ appContext.settings.getCurrentCookieBannerOptionFromSharePref() ==
+ CookieBannerOption.CookieBannerRejectAll() &&
+ action.status == EngineSession.CookieBannerHandlingStatus.HANDLED
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/components/EngineProvider.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/components/EngineProvider.kt
new file mode 100644
index 0000000000..3b747b7711
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/components/EngineProvider.kt
@@ -0,0 +1,59 @@
+/* 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.focus.components
+
+import android.content.Context
+import androidx.datastore.preferences.preferencesDataStore
+import mozilla.components.browser.engine.gecko.GeckoEngine
+import mozilla.components.browser.engine.gecko.cookiebanners.GeckoCookieBannersStorage
+import mozilla.components.browser.engine.gecko.cookiebanners.ReportSiteDomainsRepository
+import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
+import mozilla.components.concept.engine.DefaultSettings
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.fetch.Client
+import mozilla.components.lib.crash.handler.CrashHandlerService
+import org.mozilla.focus.utils.AppConstants
+import org.mozilla.geckoview.GeckoRuntime
+import org.mozilla.geckoview.GeckoRuntimeSettings
+
+object EngineProvider {
+ private var runtime: GeckoRuntime? = null
+ private val Context.dataStore by preferencesDataStore(
+ name = ReportSiteDomainsRepository.REPORT_SITE_DOMAINS_REPOSITORY_NAME,
+ )
+
+ @Synchronized
+ private fun getOrCreateRuntime(context: Context): GeckoRuntime {
+ if (runtime == null) {
+ val builder = GeckoRuntimeSettings.Builder()
+
+ builder.crashHandler(CrashHandlerService::class.java)
+ builder.aboutConfigEnabled(
+ AppConstants.isDevOrNightlyBuild || AppConstants.isBetaBuild,
+ )
+
+ runtime = GeckoRuntime.create(context, builder.build())
+ }
+
+ return runtime!!
+ }
+
+ fun createEngine(context: Context, defaultSettings: DefaultSettings): Engine {
+ val runtime = getOrCreateRuntime(context)
+
+ return GeckoEngine(context, defaultSettings, runtime)
+ }
+
+ fun createCookieBannerStorage(context: Context): GeckoCookieBannersStorage {
+ val runtime = getOrCreateRuntime(context)
+
+ return GeckoCookieBannersStorage(runtime, ReportSiteDomainsRepository(context.dataStore))
+ }
+
+ fun createClient(context: Context): Client {
+ val runtime = getOrCreateRuntime(context)
+ return GeckoViewFetchClient(context, runtime)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/contextmenu/ContextMenuCandidates.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/contextmenu/ContextMenuCandidates.kt
new file mode 100644
index 0000000000..0620e67699
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/contextmenu/ContextMenuCandidates.kt
@@ -0,0 +1,87 @@
+/* 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.focus.contextmenu
+
+import android.content.Context
+import android.view.View
+import mozilla.components.feature.app.links.AppLinksUseCases
+import mozilla.components.feature.contextmenu.ContextMenuCandidate
+import mozilla.components.feature.contextmenu.ContextMenuUseCases
+import mozilla.components.feature.tabs.TabsUseCases
+import mozilla.components.ui.widgets.DefaultSnackbarDelegate
+import mozilla.components.ui.widgets.SnackbarDelegate
+
+object ContextMenuCandidates {
+ @Suppress("LongParameterList", "UndocumentedPublicFunction")
+ fun get(
+ context: Context,
+ tabsUseCases: TabsUseCases,
+ contextMenuUseCases: ContextMenuUseCases,
+ appLinksUseCases: AppLinksUseCases,
+ snackBarParentView: View,
+ snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(),
+ isCustomTab: Boolean,
+ ): List {
+ return if (isCustomTab) {
+ // the context menu candidates list is the same as in a Fenix custom tab.
+ listOf(
+ ContextMenuCandidate.createCopyLinkCandidate(
+ context,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ ContextMenuCandidate.createShareLinkCandidate(context),
+ ContextMenuCandidate.createSaveImageCandidate(context, contextMenuUseCases),
+ ContextMenuCandidate.createSaveVideoAudioCandidate(context, contextMenuUseCases),
+ ContextMenuCandidate.createCopyImageLocationCandidate(
+ context,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ )
+ } else {
+ listOf(
+ ContextMenuCandidate.createOpenInPrivateTabCandidate(
+ context,
+ tabsUseCases,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ ContextMenuCandidate.createCopyLinkCandidate(
+ context,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ ContextMenuCandidate.createDownloadLinkCandidate(context, contextMenuUseCases),
+ ContextMenuCandidate.createShareLinkCandidate(context),
+ ContextMenuCandidate.createShareImageCandidate(context, contextMenuUseCases),
+ ContextMenuCandidate.createOpenImageInNewTabCandidate(
+ context,
+ tabsUseCases,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ ContextMenuCandidate.createSaveImageCandidate(context, contextMenuUseCases),
+ ContextMenuCandidate.createSaveVideoAudioCandidate(context, contextMenuUseCases),
+ ContextMenuCandidate.createCopyImageLocationCandidate(
+ context,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ ContextMenuCandidate.createAddContactCandidate(context),
+ ContextMenuCandidate.createShareEmailAddressCandidate(context),
+ ContextMenuCandidate.createCopyEmailAddressCandidate(
+ context,
+ snackBarParentView,
+ snackbarDelegate,
+ ),
+ ContextMenuCandidate.createOpenInExternalAppCandidate(
+ context,
+ appLinksUseCases,
+ ),
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerFragment.kt
new file mode 100644
index 0000000000..78f3d4b0f0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerFragment.kt
@@ -0,0 +1,66 @@
+/* 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.focus.cookiebanner
+
+import android.os.Bundle
+import org.mozilla.focus.GleanMetrics.CookieBanner
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.requirePreference
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsFragment
+
+class CookieBannerFragment : BaseSettingsFragment() {
+ private lateinit var rejectAllCookies: CookieBannerRejectAllPreference
+
+ override fun onStart() {
+ super.onStart()
+ showToolbar(getString(R.string.preferences_cookie_banner))
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ addPreferencesFromResource(R.xml.cookie_banner_settings)
+ setupPreferences()
+ setupInitialState()
+ setupOnPreferenceChangeListener()
+ }
+
+ private fun setupPreferences() {
+ rejectAllCookies = requirePreference(R.string.pref_key_cookie_banner_reject_all)
+ }
+
+ private fun setupInitialState() {
+ when (requireContext().settings.getCurrentCookieBannerOptionFromSharePref()) {
+ is CookieBannerOption.CookieBannerDisabled -> {
+ rejectAllCookies.isChecked = false
+ }
+ is CookieBannerOption.CookieBannerRejectAll -> {
+ rejectAllCookies.isChecked = true
+ }
+ }
+ }
+
+ private fun setupOnPreferenceChangeListener() {
+ rejectAllCookies.setOnPreferenceChangeListener { _, newValue ->
+ val enableRejectAllCookies = newValue as Boolean
+
+ val cookieBannerOption: CookieBannerOption = if (enableRejectAllCookies) {
+ CookieBannerOption.CookieBannerRejectAll()
+ } else {
+ CookieBannerOption.CookieBannerDisabled()
+ }
+
+ handleCookieBannerChange(cookieBannerOption)
+ true
+ }
+ }
+
+ private fun handleCookieBannerChange(cookieBannerOption: CookieBannerOption) {
+ CookieBanner.settingChanged.record(CookieBanner.SettingChangedExtra(cookieBannerOption.metricTag))
+ requireContext().settings.saveCurrentCookieBannerOptionInSharePref(cookieBannerOption)
+ requireContext().components.engine.settings.cookieBannerHandlingModePrivateBrowsing = cookieBannerOption.mode
+ requireContext().components.sessionUseCases.reload()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerOption.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerOption.kt
new file mode 100644
index 0000000000..69a85a2bc7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerOption.kt
@@ -0,0 +1,28 @@
+/* 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.focus.cookiebanner
+
+import mozilla.components.concept.engine.EngineSession
+import org.mozilla.focus.R
+
+sealed class CookieBannerOption(
+ open val prefKeyId: Int,
+ open val mode: EngineSession.CookieBannerHandlingMode,
+ open val metricTag: String,
+) {
+
+ data class CookieBannerRejectAll(
+ override val prefKeyId: Int = R.string.pref_key_cookie_banner_reject_all,
+ override val mode: EngineSession.CookieBannerHandlingMode =
+ EngineSession.CookieBannerHandlingMode.REJECT_ALL,
+ override val metricTag: String = "reject_all",
+ ) : CookieBannerOption(prefKeyId = prefKeyId, mode = mode, metricTag = metricTag)
+
+ data class CookieBannerDisabled(
+ override val prefKeyId: Int = R.string.pref_key_cookie_banner_disabled,
+ override val mode: EngineSession.CookieBannerHandlingMode =
+ EngineSession.CookieBannerHandlingMode.DISABLED,
+ override val metricTag: String = "disabled",
+ ) : CookieBannerOption(prefKeyId = prefKeyId, mode = mode, metricTag = metricTag)
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerRejectAllPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerRejectAllPreference.kt
new file mode 100644
index 0000000000..4d96848c43
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebanner/CookieBannerRejectAllPreference.kt
@@ -0,0 +1,20 @@
+/* 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.focus.cookiebanner
+
+import android.content.Context
+import android.util.AttributeSet
+import org.mozilla.focus.settings.LearnMoreSwitchPreference
+import org.mozilla.focus.utils.SupportUtils
+
+class CookieBannerRejectAllPreference(context: Context, attrs: AttributeSet?) :
+ LearnMoreSwitchPreference(context, attrs) {
+
+ override fun getLearnMoreUrl(): String {
+ return SupportUtils.getSumoURLForTopic(
+ SupportUtils.getAppVersion(context),
+ SupportUtils.SumoTopic.COOKIE_BANNER,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerExceptionDetailsSwitch.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerExceptionDetailsSwitch.kt
new file mode 100644
index 0000000000..084d258bf6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerExceptionDetailsSwitch.kt
@@ -0,0 +1,32 @@
+/* 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.focus.cookiebannerreducer
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.SwitchWithDescriptionBinding
+
+class CookieBannerExceptionDetailsSwitch @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ internal var binding: SwitchWithDescriptionBinding
+
+ init {
+ val view =
+ LayoutInflater.from(context).inflate(R.layout.switch_with_description, this, true)
+ binding = SwitchWithDescriptionBinding.bind(view)
+ setTitle()
+ }
+
+ private fun setTitle() {
+ binding.title.text = context.getString(R.string.cookie_banner_exception_panel_switch_title)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerDetailsPanel.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerDetailsPanel.kt
new file mode 100644
index 0000000000..9cf1aa5111
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerDetailsPanel.kt
@@ -0,0 +1,181 @@
+/* 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.focus.cookiebannerreducer
+
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.net.toUri
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import mozilla.components.support.ktx.kotlin.toShortUrl
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.CookieBanner
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.CookieBannerReducerDetailsBinding
+import org.mozilla.focus.ext.components
+
+/**
+ * Cookie banner reducer details panel that will be visible when the user
+ * clicks on cookie banner reducer item.
+ */
+class CookieBannerReducerDetailsPanel(
+ context: Context,
+ cookieBannerReducerStore: CookieBannerReducerStore,
+ private val ioScope: CoroutineScope,
+ private val tabUrl: String,
+ private val goBack: () -> Unit,
+ private val defaultCookieBannerInteractor: DefaultCookieBannerReducerInteractor,
+) : BottomSheetDialog(context) {
+
+ private var binding: CookieBannerReducerDetailsBinding =
+ CookieBannerReducerDetailsBinding.inflate(layoutInflater, null, false)
+ private val cookieBannerExceptionStatus =
+ cookieBannerReducerStore.state.cookieBannerReducerStatus
+ private var siteDomain: String? = null
+
+ init {
+ setContentView(binding.root)
+ initSiteDomain()
+ expandBottomSheet()
+ setListeners()
+ bindSwitchItem()
+ updateViews()
+ }
+
+ private fun initSiteDomain() {
+ ioScope.launch {
+ val host = tabUrl.toUri().host.orEmpty()
+ siteDomain = context.components.publicSuffixList.getPublicSuffixPlusOne(host).await()
+ }
+ }
+
+ private fun updateViews() {
+ bindTitle()
+ bindDescription()
+ bindItemAction()
+ }
+
+ private fun expandBottomSheet() {
+ val bottomSheet =
+ findViewById(R.id.design_bottom_sheet) as FrameLayout
+ BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
+ }
+
+ private fun updateSwitchItem() {
+ binding.cookieBannerExceptionDetailsSwitch.visibility = View.VISIBLE
+ binding.cookieBannerExceptionDetailsSwitch.binding.switchWidget.setOnClickListener {
+ val isChecked =
+ binding.cookieBannerExceptionDetailsSwitch.binding.switchWidget.isChecked
+ defaultCookieBannerInteractor.handleToggleCookieBannerException(isChecked)
+ updateViews()
+ dismiss()
+ }
+ }
+
+ private fun bindItemAction() {
+ when (cookieBannerExceptionStatus) {
+ CookieBannerReducerStatus.HasException -> {
+ binding.cookieBannerExceptionDetailsSwitch.binding.description.text =
+ context.getString(R.string.cookie_banner_exception_panel_switch_state_off)
+ updateSwitchItem()
+ }
+ CookieBannerReducerStatus.NoException -> {
+ binding.cookieBannerExceptionDetailsSwitch.binding.description.text =
+ context.getString(R.string.cookie_banner_exception_panel_switch_state_on)
+ updateSwitchItem()
+ }
+ CookieBannerReducerStatus.CookieBannerSiteNotSupported -> updateSiteNotSupportedItem()
+ else -> {}
+ }
+ }
+
+ private fun updateSiteNotSupportedItem() {
+ binding.requestSupport.visibility = View.VISIBLE
+ binding.cancelButton.visibility = View.VISIBLE
+ }
+
+ private fun bindTitle() {
+ val titleText = when (cookieBannerExceptionStatus) {
+ CookieBannerReducerStatus.HasException -> {
+ setExceptionTitle(
+ siteDomain,
+ R.string.cookie_banner_exception_panel_title_state_on_for_site,
+ )
+ }
+ CookieBannerReducerStatus.NoException -> {
+ setExceptionTitle(
+ siteDomain,
+ R.string.cookie_banner_exception_panel_title_state_off_for_site,
+ )
+ }
+ CookieBannerReducerStatus.CookieBannerSiteNotSupported -> {
+ context.getString(R.string.cookie_banner_exception_panel_switch_title)
+ }
+ else -> ""
+ }
+ binding.title.text = titleText
+ }
+
+ private fun bindDescription() {
+ val detailsText = when (cookieBannerExceptionStatus) {
+ CookieBannerReducerStatus.HasException -> context.getString(
+ R.string.cookie_banner_exception_panel_description_state_off_for_site2,
+ context.getString(R.string.app_name),
+ )
+ CookieBannerReducerStatus.CookieBannerSiteNotSupported -> context.getString(
+ R.string.cookie_banner_exception_panel_description_site_is_not_supported,
+ )
+ CookieBannerReducerStatus.NoException -> context.getString(
+ R.string.cookie_banner_exception_panel_description_state_on_for_site,
+ context.getString(R.string.app_name),
+ )
+ else -> ""
+ }
+ binding.details.text = detailsText
+ }
+
+ private fun setListeners() {
+ binding.detailsBack.setOnClickListener {
+ goBack.invoke()
+ dismiss()
+ }
+ binding.cancelButton.setOnClickListener {
+ CookieBanner.reportSiteCancelButton.record(NoExtras())
+ goBack.invoke()
+ dismiss()
+ }
+ binding.requestSupport.setOnClickListener {
+ if (!siteDomain.isNullOrEmpty()) {
+ defaultCookieBannerInteractor.handleRequestReportSiteDomain(siteDomain!!)
+ }
+ dismiss()
+ }
+ }
+
+ private fun setExceptionTitle(domain: String?, titleRes: Int): String {
+ val data = domain ?: tabUrl
+ val shortUrl = data.toShortUrl(context.components.publicSuffixList)
+ return context.getString(
+ titleRes,
+ shortUrl,
+ )
+ }
+
+ private fun bindSwitchItem() {
+ when (cookieBannerExceptionStatus) {
+ CookieBannerReducerStatus.HasException -> {
+ binding.cookieBannerExceptionDetailsSwitch.binding.switchWidget.isChecked = false
+ }
+ CookieBannerReducerStatus.NoException -> {
+ binding.cookieBannerExceptionDetailsSwitch.binding.switchWidget.isChecked = true
+ }
+ else -> {
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerItem.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerItem.kt
new file mode 100644
index 0000000000..6dbc2a5e77
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerItem.kt
@@ -0,0 +1,158 @@
+/* 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.focus.cookiebannerreducer
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+
+@Composable
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
+private fun CookieBannerReducerItemPreviewSiteIsNotSupported() {
+ FocusTheme {
+ CookieBannerReducerItem(cookieBannerReducerStatus = CookieBannerReducerStatus.CookieBannerSiteNotSupported) {}
+ }
+}
+
+@Composable
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
+private fun CookieBannerReducerItemPreviewHasException() {
+ FocusTheme {
+ CookieBannerReducerItem(cookieBannerReducerStatus = CookieBannerReducerStatus.HasException) {}
+ }
+}
+
+@Composable
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
+private fun CookieBannerReducerItemPreviewHasNotException() {
+ FocusTheme {
+ CookieBannerReducerItem(cookieBannerReducerStatus = CookieBannerReducerStatus.NoException) {}
+ }
+}
+
+@Composable
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
+private fun CookieBannerReducerItemPreviewUnsupportedSiteRequestWasSubmitted() {
+ FocusTheme {
+ CookieBannerReducerItem(
+ cookieBannerReducerStatus =
+ CookieBannerReducerStatus.CookieBannerUnsupportedSiteRequestWasSubmitted,
+ ) {}
+ }
+}
+
+/**
+ * Displays the cookie banner exception item from Tracking Protection panel.
+ *
+ * @param cookieBannerReducerStatus if the site has a cookie banner, an exception or is not supported
+ * @param preferenceOnClickListener Callback that will redirect the user to cookie banner item details.
+ */
+@Composable
+fun CookieBannerReducerItem(
+ cookieBannerReducerStatus: CookieBannerReducerStatus,
+ preferenceOnClickListener: (() -> Unit)? = null,
+) {
+ var rowModifier = Modifier
+ .defaultMinSize(minHeight = 48.dp)
+ .background(
+ colorResource(R.color.settings_background),
+ shape = RectangleShape,
+ )
+
+ if (cookieBannerReducerStatus !is CookieBannerReducerStatus.CookieBannerUnsupportedSiteRequestWasSubmitted
+ ) {
+ rowModifier = rowModifier.then(
+ Modifier.clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ ) { preferenceOnClickListener?.invoke() },
+ )
+ }
+
+ Row(
+ modifier = rowModifier,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val painter =
+ if (cookieBannerReducerStatus is CookieBannerReducerStatus.NoException) {
+ painterResource(id = R.drawable.mozac_ic_cookies_24)
+ } else {
+ painterResource(id = R.drawable.ic_cookies_disable)
+ }
+ Icon(
+ painter = painter,
+ contentDescription = null,
+ tint = focusColors.onPrimary,
+ modifier = Modifier.padding(end = 20.dp),
+ )
+
+ Column(
+ modifier = Modifier.weight(1f),
+ ) {
+ Text(
+ text = stringResource(R.string.cookie_banner_exception_item_title),
+ maxLines = 1,
+ color = focusColors.settingsTextColor,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ )
+ val summary = when (cookieBannerReducerStatus) {
+ CookieBannerReducerStatus.HasException ->
+ stringResource(id = R.string.cookie_banner_exception_item_description_state_off)
+ CookieBannerReducerStatus.NoException ->
+ stringResource(id = R.string.cookie_banner_exception_item_description_state_on)
+ CookieBannerReducerStatus.CookieBannerSiteNotSupported ->
+ stringResource(id = R.string.cookie_banner_exception_site_not_supported)
+ CookieBannerReducerStatus.CookieBannerUnsupportedSiteRequestWasSubmitted ->
+ stringResource(id = R.string.cookie_banner_the_site_was_reported)
+ }
+ Text(
+ text = summary,
+ maxLines = 1,
+ color = colorResource(R.color.disabled),
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ )
+ }
+ if (
+ cookieBannerReducerStatus !is CookieBannerReducerStatus.CookieBannerUnsupportedSiteRequestWasSubmitted
+ ) {
+ Icon(
+ modifier = Modifier
+ .padding(end = 0.dp)
+ .size(24.dp),
+ tint = focusColors.onPrimary,
+ painter = painterResource(id = R.drawable.mozac_ic_chevron_right_24),
+ contentDescription = null,
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerMiddleware.kt
new file mode 100644
index 0000000000..74388311a3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerMiddleware.kt
@@ -0,0 +1,230 @@
+/* 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.focus.cookiebannerreducer
+
+import android.content.Context
+import androidx.core.net.toUri
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.engine.cookiehandling.CookieBannersStorage
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.CookieBanner
+import org.mozilla.focus.GleanMetrics.Pings
+import org.mozilla.focus.cookiebanner.CookieBannerOption
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+
+/**
+ * Middleware for cookie banner reduction.
+ */
+class CookieBannerReducerMiddleware(
+ private val ioScope: CoroutineScope,
+ private val cookieBannersStorage: CookieBannersStorage,
+ private val appContext: Context,
+ private val currentTab: SessionState,
+) :
+ Middleware {
+
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (CookieBannerReducerAction) -> Unit,
+ action: CookieBannerReducerAction,
+ ) {
+ when (action) {
+ is CookieBannerReducerAction.InitCookieBannerReducer -> {
+ /**
+ * The initial CookieBannerReducerState when the user enters first in the screen
+ */
+ initCookieBannerReducer(context)
+ }
+
+ is CookieBannerReducerAction.ToggleCookieBannerException -> {
+ handleCookieBannerToggle(action, context)
+ next(action)
+ }
+ is CookieBannerReducerAction.RequestReportSite -> {
+ reportSite(action, context)
+ next(action)
+ }
+ else -> {
+ next(action)
+ }
+ }
+ }
+
+ private fun handleCookieBannerToggle(
+ action: CookieBannerReducerAction.ToggleCookieBannerException,
+ context: MiddlewareContext,
+ ) {
+ ioScope.launch {
+ if (action.isCookieBannerHandlingExceptionEnabled) {
+ cookieBannersStorage.removeException(currentTab.content.url, true)
+ CookieBanner.exceptionRemoved.record(NoExtras())
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.NoException,
+ ),
+ )
+ } else {
+ clearSiteData()
+ cookieBannersStorage.addPersistentExceptionInPrivateMode(currentTab.content.url)
+ CookieBanner.exceptionAdded.record(NoExtras())
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.HasException,
+ ),
+ )
+ }
+ appContext.components.sessionUseCases.reload()
+ }
+ }
+
+ private fun reportSite(
+ action: CookieBannerReducerAction.RequestReportSite,
+ context: MiddlewareContext,
+ ) {
+ CookieBanner.reportSiteDomain.set(action.siteToReport)
+ Pings.cookieBannerReportSite.submit()
+ context.store.dispatch(
+ CookieBannerReducerAction.ShowSnackBarForSiteToReport(
+ true,
+ ),
+ )
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.CookieBannerUnsupportedSiteRequestWasSubmitted,
+ ),
+ )
+ ioScope.launch { cookieBannersStorage.saveSiteDomain(action.siteToReport) }
+ }
+
+ private fun initCookieBannerReducer(
+ context: MiddlewareContext,
+ ) {
+ val shouldShowCookieBannerItem = shouldShowCookieBannerReducerItem()
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerVisibility(
+ shouldShowCookieBannerItem = shouldShowCookieBannerItem,
+ ),
+ )
+
+ if (!shouldShowCookieBannerItem) {
+ return
+ }
+ ioScope.launch {
+ if (isSiteDomainReported(context)) {
+ return@launch
+ }
+ val hasException =
+ cookieBannersStorage.hasException(currentTab.content.url, true)
+ withContext(Dispatchers.Main) {
+ if (hasException == null) {
+ // An error occurred while querying the exception, let's hide the item.
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ null,
+ ),
+ )
+ return@withContext
+ } else if (!hasException) {
+ showUnsupportedSiteIfNeeded(context)
+ } else {
+ showExceptionStatus(context, true)
+ }
+ }
+ }
+ }
+
+ private fun showUnsupportedSiteIfNeeded(
+ context: MiddlewareContext,
+ ) {
+ currentTab.engineState.engineSession?.hasCookieBannerRuleForSession(
+ onResult = { result ->
+ if (result) {
+ showExceptionStatus(context, false)
+ } else {
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.CookieBannerSiteNotSupported,
+ ),
+ )
+ }
+ },
+ onException = {
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerVisibility(
+ shouldShowCookieBannerItem = false,
+ ),
+ )
+ },
+ )
+ }
+
+ private fun showExceptionStatus(
+ context: MiddlewareContext,
+ hasException: Boolean,
+ ) {
+ if (hasException) {
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.HasException,
+ ),
+ )
+ } else {
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.NoException,
+ ),
+ )
+ }
+ }
+
+ /**
+ * It returns the cookie banner reducer item visibility from tracking protection panel .
+ * If the item is invisible item details should also be invisible.
+ */
+ private fun shouldShowCookieBannerReducerItem(): Boolean {
+ return appContext.settings.isCookieBannerEnable &&
+ appContext.settings.getCurrentCookieBannerOptionFromSharePref() !=
+ CookieBannerOption.CookieBannerDisabled()
+ }
+
+ private suspend fun isSiteDomainReported(
+ context: MiddlewareContext,
+ ): Boolean {
+ val host = currentTab.content.url.toUri().host.orEmpty()
+ val siteDomain =
+ appContext.components.publicSuffixList.getPublicSuffixPlusOne(host).await()
+ if (siteDomain != null && cookieBannersStorage.isSiteDomainReported(siteDomain)) {
+ context.store.dispatch(
+ CookieBannerReducerAction.UpdateCookieBannerReducerStatus(
+ CookieBannerReducerStatus.CookieBannerUnsupportedSiteRequestWasSubmitted,
+ ),
+ )
+ return true
+ }
+ return false
+ }
+
+ private suspend fun clearSiteData() {
+ val host = currentTab.content.url.toUri().host.orEmpty()
+ val domain = appContext.components.publicSuffixList.getPublicSuffixPlusOne(host).await()
+ withContext(Dispatchers.Main) {
+ appContext.components.engine.clearData(
+ host = domain,
+ data = Engine.BrowsingData.select(
+ Engine.BrowsingData.AUTH_SESSIONS,
+ Engine.BrowsingData.ALL_SITE_DATA,
+ ),
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStatus.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStatus.kt
new file mode 100644
index 0000000000..978a0baf50
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStatus.kt
@@ -0,0 +1,32 @@
+/* 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.focus.cookiebannerreducer
+
+/**
+ * Sealed class for the cookie banner exception Gui item
+ * from Tracking Protection panel.
+ */
+sealed class CookieBannerReducerStatus {
+
+ /**
+ * If the site is excepted from cookie banner reduction.
+ */
+ object HasException : CookieBannerReducerStatus()
+
+ /**
+ * If the site is not excepted from cookie banner reduction.
+ */
+ object NoException : CookieBannerReducerStatus()
+
+ /**
+ * If the cookie banner reducer is not supported on the site.
+ */
+ object CookieBannerSiteNotSupported : CookieBannerReducerStatus()
+
+ /**
+ * If the user reports with success a site that wasn't supported by cookie banner reducer.
+ */
+ object CookieBannerUnsupportedSiteRequestWasSubmitted : CookieBannerReducerStatus()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStore.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStore.kt
new file mode 100644
index 0000000000..b6225bb028
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/CookieBannerReducerStore.kt
@@ -0,0 +1,113 @@
+/* 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.focus.cookiebannerreducer
+
+import mozilla.components.lib.state.Action
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.State
+import mozilla.components.lib.state.Store
+
+/**
+ * The [CookieBannerReducerStore] holds the [CookieBannerReducerState] (state tree).
+ *
+ * The only way to change the [CookieBannerReducerState] inside
+ * [CookieBannerReducerStore] is to dispatch an [CookieBannerReducerAction] on it.
+ */
+class CookieBannerReducerStore(
+ initialState: CookieBannerReducerState,
+ middlewares: List> = emptyList(),
+) : Store(
+ initialState,
+ ::cookieBannerStateReducer,
+ middlewares,
+) {
+ init {
+ dispatch(CookieBannerReducerAction.InitCookieBannerReducer)
+ }
+}
+
+/**
+ * The state of the cookie banner reducer
+ *
+ * @property isCookieBannerToggleEnabled Current status of cookie banner toggle from details exception.
+ * @property shouldShowCookieBannerItem Visibility of cookie banner reducer item.
+ * @property isCookieBannerDetected If the site has a cookie banner.
+ * @property showSnackBarForSiteToReport When cookie banner reducer doesn't work
+ * on a website and the user reports that site
+ * @property cookieBannerReducerStatus Current status of cookie banner reducer.
+ * @property siteToReport Site to report when cookie banner reducer doesn't work
+ */
+data class CookieBannerReducerState(
+ val isCookieBannerToggleEnabled: Boolean = false,
+ val shouldShowCookieBannerItem: Boolean = false,
+ val isCookieBannerDetected: Boolean = false,
+ val showSnackBarForSiteToReport: Boolean = false,
+ val cookieBannerReducerStatus: CookieBannerReducerStatus? = CookieBannerReducerStatus.NoException,
+ val siteToReport: String = "",
+) : State
+
+/**
+ * Action to dispatch through the `CookieBannerReducerStore` to modify cookie banner reducer item and item details
+ * from Tracking protection panel through the reducer.
+ */
+@Suppress("UndocumentedPublicClass")
+sealed class CookieBannerReducerAction : Action {
+ object InitCookieBannerReducer : CookieBannerReducerAction()
+
+ data class ToggleCookieBannerException(
+ val isCookieBannerHandlingExceptionEnabled: Boolean,
+ ) : CookieBannerReducerAction()
+
+ data class UpdateCookieBannerReducerVisibility(
+ val shouldShowCookieBannerItem: Boolean,
+ ) : CookieBannerReducerAction()
+
+ data class UpdateCookieBannerReducerStatus(
+ val cookieBannerReducerStatus: CookieBannerReducerStatus?,
+ ) : CookieBannerReducerAction()
+
+ data class RequestReportSite(
+ val siteToReport: String,
+ ) : CookieBannerReducerAction()
+
+ data class ShowSnackBarForSiteToReport(
+ val isSnackBarVisible: Boolean,
+ ) : CookieBannerReducerAction()
+}
+
+/**
+ * Reduces the cookie banner state from the current state and an action performed on it.
+ *
+ * @param state the current cookie banner item state
+ * @param action the action to perform
+ * @return the new cookie banner reducer state
+ */
+private fun cookieBannerStateReducer(
+ state: CookieBannerReducerState,
+ action: CookieBannerReducerAction,
+): CookieBannerReducerState {
+ return when (action) {
+ is CookieBannerReducerAction.ToggleCookieBannerException -> {
+ state.copy(isCookieBannerToggleEnabled = action.isCookieBannerHandlingExceptionEnabled)
+ }
+ is CookieBannerReducerAction.UpdateCookieBannerReducerVisibility -> {
+ state.copy(shouldShowCookieBannerItem = action.shouldShowCookieBannerItem)
+ }
+ is CookieBannerReducerAction.UpdateCookieBannerReducerStatus -> {
+ state.copy(cookieBannerReducerStatus = action.cookieBannerReducerStatus)
+ }
+ is CookieBannerReducerAction.RequestReportSite -> {
+ state.copy(siteToReport = action.siteToReport)
+ }
+ is CookieBannerReducerAction.ShowSnackBarForSiteToReport -> {
+ state.copy(showSnackBarForSiteToReport = action.isSnackBarVisible)
+ }
+ CookieBannerReducerAction.InitCookieBannerReducer -> {
+ throw IllegalStateException(
+ "You need to add CookieBannerReducerMiddleware to your CookieBannerReducerStore. ($action)",
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/DefaultCookieBannerReducerInteractor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/DefaultCookieBannerReducerInteractor.kt
new file mode 100644
index 0000000000..f0ee6f7305
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/cookiebannerreducer/DefaultCookieBannerReducerInteractor.kt
@@ -0,0 +1,35 @@
+/* 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.focus.cookiebannerreducer
+
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.CookieBanner
+
+/**
+ * Interactor class for cookie banner reducer feature .
+ */
+class DefaultCookieBannerReducerInteractor(val store: CookieBannerReducerStore) {
+
+ /**
+ * Method that gets called when the user changes the cookie banner exception toggle state.
+ * @param isCookieBannerHandlingExceptionEnabled - the state of the toggle
+ */
+ fun handleToggleCookieBannerException(isCookieBannerHandlingExceptionEnabled: Boolean) {
+ store.dispatch(
+ CookieBannerReducerAction.ToggleCookieBannerException(
+ isCookieBannerHandlingExceptionEnabled,
+ ),
+ )
+ }
+
+ /**
+ * Method that gets called when the user sends the url of the site that he wants to report.
+ * @param siteDomain - the site domain that will be sent to nimbus.
+ */
+ fun handleRequestReportSiteDomain(siteDomain: String) {
+ CookieBanner.reportDomainSiteButton.record(NoExtras())
+ store.dispatch(CookieBannerReducerAction.RequestReportSite(siteToReport = siteDomain))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/customtabs/CustomTabsService.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/customtabs/CustomTabsService.kt
new file mode 100644
index 0000000000..73a453cc89
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/customtabs/CustomTabsService.kt
@@ -0,0 +1,15 @@
+/* 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.focus.customtabs
+
+import mozilla.components.concept.engine.Engine
+import mozilla.components.feature.customtabs.AbstractCustomTabsService
+import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
+import org.mozilla.focus.ext.components
+
+class CustomTabsService : AbstractCustomTabsService() {
+ override val customTabsServiceStore: CustomTabsServiceStore by lazy { components.customTabsStore }
+ override val engine: Engine by lazy { components.engine }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/downloads/DownloadService.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/downloads/DownloadService.kt
new file mode 100644
index 0000000000..b5041f573e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/downloads/DownloadService.kt
@@ -0,0 +1,17 @@
+/* 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.focus.downloads
+
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.fetch.Client
+import mozilla.components.feature.downloads.AbstractFetchDownloadService
+import mozilla.components.support.base.android.NotificationsDelegate
+import org.mozilla.focus.ext.components
+
+class DownloadService : AbstractFetchDownloadService() {
+ override val httpClient: Client by lazy { components.client }
+ override val store: BrowserStore by lazy { components.store }
+ override val notificationsDelegate: NotificationsDelegate by lazy { components.notificationsDelegate }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/AppContentInterceptor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/AppContentInterceptor.kt
new file mode 100644
index 0000000000..2f6e4c5147
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/AppContentInterceptor.kt
@@ -0,0 +1,111 @@
+/* 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.focus.engine
+
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import mozilla.components.browser.errorpages.ErrorPages
+import mozilla.components.browser.errorpages.ErrorType
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.request.RequestInterceptor
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.CrashListActivity
+import org.mozilla.focus.browser.LocalizedContent
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.utils.SupportUtils
+
+class AppContentInterceptor(
+ private val context: Context,
+) : RequestInterceptor {
+ override fun onLoadRequest(
+ engineSession: EngineSession,
+ uri: String,
+ lastUri: String?,
+ hasUserGesture: Boolean,
+ isSameDomain: Boolean,
+ isRedirect: Boolean,
+ isDirectNavigation: Boolean,
+ isSubframeRequest: Boolean,
+ ): RequestInterceptor.InterceptionResponse? {
+ return when (uri) {
+ LocalizedContent.URL_ABOUT -> RequestInterceptor.InterceptionResponse.Content(
+ LocalizedContent.loadAbout(context),
+ encoding = "base64",
+ )
+
+ LocalizedContent.URL_RIGHTS -> RequestInterceptor.InterceptionResponse.Content(
+ LocalizedContent.loadRights(context),
+ encoding = "base64",
+ )
+
+ LocalizedContent.URL_GPL -> RequestInterceptor.InterceptionResponse.Content(
+ LocalizedContent.loadGPL(context),
+ encoding = "base64",
+ )
+
+ LocalizedContent.URL_LICENSES -> RequestInterceptor.InterceptionResponse.Content(
+ LocalizedContent.loadLicenses(context),
+ encoding = "base64",
+ )
+
+ "about:crashes" -> {
+ val intent = Intent(context, CrashListActivity::class.java)
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+
+ RequestInterceptor.InterceptionResponse.Url("about:blank")
+ }
+
+ else -> context.components.appLinksInterceptor.onLoadRequest(
+ engineSession,
+ uri,
+ lastUri,
+ hasUserGesture,
+ isSameDomain,
+ isRedirect,
+ isDirectNavigation,
+ isSubframeRequest,
+ )
+ }
+ }
+
+ override fun onErrorRequest(
+ session: EngineSession,
+ errorType: ErrorType,
+ uri: String?,
+ ): RequestInterceptor.ErrorResponse {
+ val errorPage = ErrorPages.createUrlEncodedErrorPage(
+ context,
+ errorType,
+ uri,
+ titleOverride = { type -> getErrorPageTitle(context, type) },
+ descriptionOverride = { type -> getErrorPageDescription(context, type) },
+ )
+ return RequestInterceptor.ErrorResponse(errorPage)
+ }
+
+ override fun interceptsAppInitiatedRequests() = true
+}
+
+private fun getErrorPageTitle(context: Context, type: ErrorType): String? {
+ if (type == ErrorType.ERROR_HTTPS_ONLY) {
+ return context.getString(R.string.errorpage_httpsonly_title2)
+ }
+ // Returning `null` here will let the component use its default title for this error type
+ return null
+}
+
+private fun getErrorPageDescription(context: Context, type: ErrorType): String? {
+ if (type == ErrorType.ERROR_HTTPS_ONLY) {
+ return context.getString(
+ R.string.errorpage_httpsonly_message2,
+ context.getString(R.string.app_name),
+ SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.HTTPS_ONLY),
+ )
+ }
+ // Returning `null` here will let the component use its default description for this error type
+ return null
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/ClientWrapper.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/ClientWrapper.kt
new file mode 100644
index 0000000000..092744cd52
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/ClientWrapper.kt
@@ -0,0 +1,29 @@
+/* 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.focus.engine
+
+import mozilla.components.concept.fetch.Client
+import mozilla.components.concept.fetch.Request
+import mozilla.components.concept.fetch.Response
+
+/**
+ * A wrapper around [Client] preventing [Request]s without the private flag set.
+ */
+class ClientWrapper(
+ private val actual: Client,
+) : Client() {
+ override fun fetch(request: Request): Response {
+ if (!request.private) {
+ throw IllegalStateException("Non-private request")
+ }
+
+ return actual.fetch(request)
+ }
+
+ @Deprecated("Non-private Client usage should be prevented")
+ fun unwrap(): Client {
+ return actual
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/EngineSharedPreferencesListener.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/EngineSharedPreferencesListener.kt
new file mode 100644
index 0000000000..abd64b8e92
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/EngineSharedPreferencesListener.kt
@@ -0,0 +1,95 @@
+/* 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.focus.engine
+
+import android.content.Context
+import androidx.preference.Preference
+import org.mozilla.focus.GleanMetrics.TrackingProtection
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+
+/**
+ * SharedPreference listener that will update the engine whenever the user changes settings.
+ */
+class EngineSharedPreferencesListener(
+ private val context: Context,
+) : Preference.OnPreferenceChangeListener {
+
+ override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
+ when (preference.key) {
+ context.getString(R.string.pref_key_performance_enable_cookies) ->
+ updateTrackingProtectionPolicy(shouldBlockCookiesValue = newValue as String)
+
+ context.getString(R.string.pref_key_safe_browsing) ->
+ updateSafeBrowsingPolicy(newValue as Boolean)
+
+ context.getString(R.string.pref_key_performance_block_javascript) ->
+ updateJavaScriptSetting(newValue as Boolean)
+
+ context.getString(R.string.pref_key_performance_block_webfonts) ->
+ updateWebFontsBlocking(newValue as Boolean)
+ }
+
+ return true
+ }
+
+ internal fun updateTrackingProtectionPolicy(
+ source: String? = null,
+ tracker: String? = null,
+ isEnabled: Boolean = false,
+ shouldBlockCookiesValue: String = context.settings.shouldBlockCookiesValue(),
+ ) {
+ val policy = context.settings.createTrackingProtectionPolicy(shouldBlockCookiesValue)
+ val components = context.components
+
+ components.engineDefaultSettings.trackingProtectionPolicy = policy
+ components.settingsUseCases.updateTrackingProtection(policy)
+
+ if (source != null && tracker != null) {
+ TrackingProtection.trackerSettingChanged.record(
+ TrackingProtection.TrackerSettingChangedExtra(
+ sourceOfChange = source,
+ trackerChanged = tracker,
+ isEnabled = isEnabled,
+ ),
+ )
+ }
+ components.sessionUseCases.reload()
+ }
+
+ private fun updateSafeBrowsingPolicy(newValue: Boolean) {
+ context.settings.setupSafeBrowsing(context.components.engine, newValue)
+ context.components.sessionUseCases.reload()
+ }
+
+ private fun updateJavaScriptSetting(newValue: Boolean) {
+ val components = context.components
+
+ components.engineDefaultSettings.javascriptEnabled = !newValue
+ components.engine.settings.javascriptEnabled = !newValue
+ components.sessionUseCases.reload()
+ }
+
+ private fun updateWebFontsBlocking(newValue: Boolean) {
+ val components = context.components
+
+ components.engineDefaultSettings.webFontsEnabled = !newValue
+ components.engine.settings.webFontsEnabled = !newValue
+ components.sessionUseCases.reload()
+ }
+
+ enum class ChangeSource(val source: String) {
+ SETTINGS("Settings"),
+ PANEL("Panel"),
+ }
+
+ enum class TrackerChanged(val tracker: String) {
+ ADVERTISING("Advertising"),
+ ANALYTICS("Analytics"),
+ SOCIAL("Social"),
+ CONTENT("Content"),
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/SanityCheckMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/SanityCheckMiddleware.kt
new file mode 100644
index 0000000000..c84c7b2fc6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/engine/SanityCheckMiddleware.kt
@@ -0,0 +1,36 @@
+/* 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.focus.engine
+
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.InitAction
+import mozilla.components.browser.state.action.TabListAction
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+
+/**
+ * Middleware preventing creating non-private tabs.
+ */
+class SanityCheckMiddleware : Middleware {
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ next(action)
+
+ if (action is TabListAction || action is InitAction) {
+ verifyNoNonPrivateTabs(context.state)
+ }
+ }
+
+ private fun verifyNoNonPrivateTabs(state: BrowserState) {
+ if (state.normalTabs.isNotEmpty()) {
+ throw IllegalStateException("State contains non-private tabs")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsListFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsListFragment.kt
new file mode 100644
index 0000000000..eecbc7f517
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsListFragment.kt
@@ -0,0 +1,323 @@
+/* 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.focus.exceptions
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.CompoundButton
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
+import org.mozilla.focus.GleanMetrics.TrackingProtectionExceptions
+import org.mozilla.focus.R
+import org.mozilla.focus.autocomplete.AutocompleteDomainFormatter
+import org.mozilla.focus.databinding.FragmentExceptionsDomainsBinding
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsLikeFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.utils.ViewUtils
+import java.util.Collections
+import kotlin.coroutines.CoroutineContext
+
+private const val REMOVE_EXCEPTIONS_DISABLED_ALPHA = 0.5f
+typealias DomainFormatter = (String) -> String
+
+/**
+ * Fragment showing settings UI listing all exception domains.
+ */
+open class ExceptionsListFragment : BaseSettingsLikeFragment(), CoroutineScope {
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+ private var _binding: FragmentExceptionsDomainsBinding? = null
+ protected val binding get() = _binding!!
+
+ /**
+ * ItemTouchHelper for reordering items in the domain list.
+ */
+ val itemTouchHelper: ItemTouchHelper = ItemTouchHelper(
+ object : SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
+ override fun onMove(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder,
+ ): Boolean {
+ val from = viewHolder.bindingAdapterPosition
+ val to = target.bindingAdapterPosition
+
+ (recyclerView.adapter as DomainListAdapter).move(from, to)
+
+ return true
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
+
+ override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
+ super.onSelectedChanged(viewHolder, actionState)
+
+ if (viewHolder is DomainViewHolder) {
+ viewHolder.onSelected()
+ }
+ }
+
+ override fun clearView(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ ) {
+ super.clearView(recyclerView, viewHolder)
+
+ if (viewHolder is DomainViewHolder) {
+ viewHolder.onCleared()
+ }
+ }
+ },
+ )
+
+ /**
+ * In selection mode the user can select and remove items. In non-selection mode the list can
+ * be reordered by the user.
+ */
+ open fun isSelectionMode() = false
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentExceptionsDomainsBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.exceptionList.apply {
+ layoutManager =
+ LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
+ adapter = DomainListAdapter()
+ setHasFixedSize(true)
+ }
+
+ binding.removeAllExceptions.isVisible = !isSelectionMode()
+
+ if (!isSelectionMode()) {
+ itemTouchHelper.attachToRecyclerView(binding.exceptionList)
+ }
+
+ binding.removeAllExceptions.setOnClickListener { removeButton ->
+ removeButton.apply {
+ isEnabled = false
+ alpha = REMOVE_EXCEPTIONS_DISABLED_ALPHA
+ }
+ requireComponents.trackingProtectionUseCases.removeAllExceptions {
+ val exceptionsListSize =
+ (binding.exceptionList.adapter as DomainListAdapter).itemCount
+ TrackingProtectionExceptions.allowListCleared.record(
+ TrackingProtectionExceptions.AllowListClearedExtra(exceptionsListSize),
+ )
+
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(
+ requireComponents.store.state.selectedTabId,
+ ),
+ )
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ job = Job()
+
+ showToolbar(getString(R.string.preference_exceptions))
+
+ (binding.exceptionList.adapter as DomainListAdapter).refresh(requireActivity()) {
+ // check if the exceptions list is empty only if fragment is still attached.
+ context?.let {
+ if ((binding.exceptionList.adapter as DomainListAdapter).itemCount == 0) {
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(requireComponents.store.state.selectedTabId),
+ )
+ }
+ activity?.invalidateOptionsMenu()
+ }
+ }
+ }
+
+ override fun onStop() {
+ job.cancel()
+ super.onStop()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_exceptions_list, menu)
+ }
+
+ override fun onPrepareMenu(menu: Menu) {
+ val removeItem = menu.findItem(R.id.remove)
+
+ removeItem?.let {
+ it.isVisible = isSelectionMode() || binding.exceptionList.adapter!!.itemCount > 0
+ val isEnabled =
+ !isSelectionMode() || (binding.exceptionList.adapter as DomainListAdapter).selection().isNotEmpty()
+ ViewUtils.setMenuItemEnabled(it, isEnabled)
+ }
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
+ R.id.remove -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.PrivacyExceptionsRemove),
+ )
+ true
+ }
+ else -> false
+ }
+
+ /**
+ * Adapter implementation for the list of exception domains.
+ */
+ inner class DomainListAdapter : RecyclerView.Adapter() {
+ private var exceptions: List = emptyList()
+ private val selectedExceptions: MutableList = mutableListOf()
+
+ fun refresh(context: Context, body: (() -> Unit)? = null) {
+ this@ExceptionsListFragment.launch(Dispatchers.Main) {
+ context.components.trackingProtectionUseCases.fetchExceptions {
+ exceptions = it
+ notifyDataSetChanged()
+ body?.invoke()
+ }
+ }
+ }
+
+ override fun getItemViewType(position: Int) = DomainViewHolder.LAYOUT_ID
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
+ when (viewType) {
+ DomainViewHolder.LAYOUT_ID ->
+ DomainViewHolder(
+ LayoutInflater.from(parent.context).inflate(viewType, parent, false),
+ ) { AutocompleteDomainFormatter.format(it) }
+ else -> throw IllegalArgumentException("Unknown view type: $viewType")
+ }
+
+ override fun getItemCount(): Int = exceptions.size
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ if (holder is DomainViewHolder) {
+ holder.bind(
+ exceptions[position],
+ isSelectionMode(),
+ selectedExceptions,
+ itemTouchHelper,
+ this@ExceptionsListFragment,
+ )
+ }
+ }
+
+ override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
+ if (holder is DomainViewHolder) {
+ holder.checkBoxView.setOnCheckedChangeListener(null)
+ }
+ }
+
+ fun selection(): List = selectedExceptions
+
+ fun move(from: Int, to: Int) {
+ Collections.swap(exceptions, from, to)
+ notifyItemMoved(from, to)
+
+ // The underlying storage in GeckoView doesn't support ordering - and ordering is also
+ // not necessary. We may just need to remove this feature from this list.
+ }
+ }
+
+ /**
+ * ViewHolder implementation for a domain item in the list.
+ */
+ private class DomainViewHolder(
+ itemView: View,
+ val domainFormatter: DomainFormatter? = null,
+ ) : RecyclerView.ViewHolder(itemView) {
+ val domainView: TextView = itemView.findViewById(R.id.domainView)
+ val checkBoxView: CheckBox = itemView.findViewById(R.id.checkbox)
+ val handleView: View = itemView.findViewById(R.id.handleView)
+
+ companion object {
+ val LAYOUT_ID = R.layout.item_custom_domain
+ }
+
+ fun bind(
+ exception: TrackingProtectionException,
+ isSelectionMode: Boolean,
+ selectedExceptions: MutableList,
+ itemTouchHelper: ItemTouchHelper,
+ fragment: ExceptionsListFragment,
+ ) {
+ domainView.text = domainFormatter?.invoke(exception.url) ?: exception.url
+
+ checkBoxView.isVisible = isSelectionMode
+ checkBoxView.isChecked = selectedExceptions.contains(exception)
+ checkBoxView.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
+ if (isChecked) {
+ selectedExceptions.add(exception)
+ } else {
+ selectedExceptions.remove(exception)
+ }
+
+ fragment.activity?.invalidateOptionsMenu()
+ }
+
+ handleView.isVisible = isSelectionMode
+ handleView.setOnTouchListener { _, event ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ itemTouchHelper.startDrag(this)
+ }
+ false
+ }
+
+ if (isSelectionMode) {
+ itemView.setOnClickListener {
+ checkBoxView.isChecked = !checkBoxView.isChecked
+ }
+ }
+ }
+
+ fun onSelected() {
+ itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.disabled))
+ }
+
+ fun onCleared() {
+ itemView.setBackgroundColor(0)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsRemoveFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsRemoveFragment.kt
new file mode 100644
index 0000000000..c228877556
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/exceptions/ExceptionsRemoveFragment.kt
@@ -0,0 +1,61 @@
+/* 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.focus.exceptions
+
+import android.content.Context
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.launch
+import org.mozilla.focus.GleanMetrics.TrackingProtectionExceptions
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.state.AppAction
+import kotlin.collections.forEach as withEach
+
+class ExceptionsRemoveFragment : ExceptionsListFragment() {
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_autocomplete_remove, menu)
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
+ R.id.remove -> {
+ removeSelectedDomains(requireActivity().applicationContext)
+ true
+ }
+ else -> false
+ }
+
+ private fun removeSelectedDomains(context: Context) {
+ val exceptions = (binding.exceptionList.adapter as DomainListAdapter).selection()
+ TrackingProtectionExceptions.selectedItemsRemoved.record(
+ TrackingProtectionExceptions.SelectedItemsRemovedExtra(exceptions.size),
+ )
+
+ if (exceptions.isNotEmpty()) {
+ launch(Main) {
+ exceptions.withEach { exception ->
+ context.components.trackingProtectionUseCases.removeException(exception)
+ }
+
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(requireComponents.store.state.selectedTabId),
+ )
+ }
+ }
+ }
+
+ override fun isSelectionMode() = true
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.preference_autocomplete_title_remove))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/experiments/NimbusSetup.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/experiments/NimbusSetup.kt
new file mode 100644
index 0000000000..07b788d2d6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/experiments/NimbusSetup.kt
@@ -0,0 +1,116 @@
+/* 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.focus.experiments
+
+import android.content.Context
+import mozilla.components.service.nimbus.NimbusApi
+import mozilla.components.service.nimbus.NimbusAppInfo
+import mozilla.components.service.nimbus.NimbusBuilder
+import mozilla.components.support.base.log.logger.Logger
+import org.json.JSONObject
+import org.mozilla.experiments.nimbus.NimbusInterface
+import org.mozilla.experiments.nimbus.internal.NimbusException
+import org.mozilla.focus.BuildConfig
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.nimbus.FocusNimbus
+
+/**
+ * The maximum amount of time the app launch will be blocked to load experiments from disk.
+ *
+ * ⚠️ This value was decided from analyzing the Focus metrics (nimbus_initial_fetch) for the ideal
+ * timeout. We should NOT change this value without collecting more metrics first.
+ */
+private const val TIME_OUT_LOADING_EXPERIMENT_FROM_DISK_MS = 200L
+
+/**
+ * Create the Nimbus singleton object for the Focus/Klar apps.
+ */
+fun createNimbus(context: Context, urlString: String?): NimbusApi {
+ val isAppFirstRun = context.settings.isFirstRun
+
+ // These values can be used in the JEXL expressions when targeting experiments.
+ val customTargetingAttributes = JSONObject().apply {
+ // By convention, we should use snake case.
+ put("is_first_run", isAppFirstRun)
+
+ // This camelCase attribute is a boolean value represented as a string.
+ // This is left for backwards compatibility.
+ put("isFirstRun", isAppFirstRun.toString())
+ }
+
+ // The name "focus-android" or "klar-android" here corresponds to the app_name defined
+ // for the family of apps that encompasses all of the channels for the Focus app.
+ // This is defined upstream in the telemetry system. For more context on where the
+ // app_name come from see:
+ // https://probeinfo.telemetry.mozilla.org/v2/glean/app-listings
+ // and
+ // https://github.com/mozilla/probe-scraper/blob/master/repositories.yaml
+ val appInfo = NimbusAppInfo(
+ appName = getNimbusAppName(),
+ // Note: Using BuildConfig.BUILD_TYPE is important here so that it matches the value
+ // passed into Glean. `Config.channel.toString()` turned out to be non-deterministic
+ // and would mostly produce the value `Beta` and rarely would produce `beta`.
+ channel = BuildConfig.BUILD_TYPE,
+ customTargetingAttributes = customTargetingAttributes,
+ )
+
+ return NimbusBuilder(context).apply {
+ url = urlString
+ errorReporter = { message, e ->
+ Logger.error("Nimbus error: $message", e)
+ if (e !is NimbusException || e.isReportableError()) {
+ context.components.crashReporter.submitCaughtException(e)
+ }
+ }
+ initialExperiments = R.raw.initial_experiments
+ timeoutLoadingExperiment = TIME_OUT_LOADING_EXPERIMENT_FROM_DISK_MS
+ usePreviewCollection = context.settings.shouldUseNimbusPreview
+ sharedPreferences = context.settings.preferences
+ isFirstRun = isAppFirstRun
+ featureManifest = FocusNimbus
+ }.build(appInfo)
+}
+
+internal fun finishNimbusInitialization(experiments: NimbusApi) =
+ experiments.run {
+ // We fetch experiments in all cases,
+ if (context.settings.isFirstRun) {
+ // … however on first run, we immediately apply pending experiments.
+ // We also want to measure how long this will take, with Glean.
+ register(
+ object : NimbusInterface.Observer {
+ override fun onExperimentsFetched() {
+ applyPendingExperiments()
+ // Remove lingering observer when we're done fetching experiments on startup.
+ unregister(this)
+ }
+ },
+ )
+ }
+ fetchExperiments()
+ }
+
+fun getNimbusAppName(): String {
+ return if (BuildConfig.FLAVOR.contains("focus")) {
+ "focus_android"
+ } else {
+ "klar_android"
+ }
+}
+
+/**
+ * Classifies which errors we should forward to our crash reporter or not. We want to filter out the
+ * non-reportable ones if we know there is no reasonable action that we can perform.
+ *
+ * This fix should be upstreamed as part of: https://github.com/mozilla/application-services/issues/4333
+ */
+fun NimbusException.isReportableError(): Boolean {
+ return when (this) {
+ is NimbusException.ClientException -> false
+ else -> true
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Activity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Activity.kt
new file mode 100644
index 0000000000..ee391ceb0c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Activity.kt
@@ -0,0 +1,41 @@
+/* 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.focus.ext
+
+import android.app.Activity
+import android.os.Build
+import android.view.WindowManager
+import androidx.annotation.DrawableRes
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * Sets the icon for the back (up) navigation button.
+ * @param icon The resource id of the icon.
+ */
+fun Activity.setNavigationIcon(
+ @DrawableRes icon: Int,
+) {
+ (this as? AppCompatActivity)?.supportActionBar?.let {
+ it.setDisplayHomeAsUpEnabled(true)
+ it.setHomeAsUpIndicator(icon)
+ }
+}
+
+/**
+ * Sets or clears the secure flags for the activity's window.
+ */
+fun Activity.updateSecureWindowFlags() {
+ if (this.settings.shouldUseSecureMode()) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ setRecentsScreenshotEnabled(false)
+ }
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ setRecentsScreenshotEnabled(true)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/AndroidViewModel.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/AndroidViewModel.kt
new file mode 100644
index 0000000000..216dc009e7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/AndroidViewModel.kt
@@ -0,0 +1,12 @@
+/* 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.focus.ext
+
+import androidx.lifecycle.AndroidViewModel
+import org.mozilla.focus.Components
+import org.mozilla.focus.FocusApplication
+
+val AndroidViewModel.components: Components
+ get() = getApplication().components
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserStore.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserStore.kt
new file mode 100644
index 0000000000..e037f3f368
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserStore.kt
@@ -0,0 +1,21 @@
+/* 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.focus.ext
+
+import mozilla.components.browser.state.search.SearchEngine
+import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
+import mozilla.components.browser.state.store.BrowserStore
+
+/**
+ * Returns the default search engine name or "custom" string if the engine is added by the user.
+ */
+fun BrowserStore.defaultSearchEngineName(): String {
+ val defaultSearchEngine = state.search.selectedOrDefaultSearchEngine
+ return if (defaultSearchEngine?.type == SearchEngine.Type.CUSTOM) {
+ "custom"
+ } else {
+ defaultSearchEngine?.name ?: ""
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserToolbar.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserToolbar.kt
new file mode 100644
index 0000000000..e719986a9d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/BrowserToolbar.kt
@@ -0,0 +1,86 @@
+/* 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.focus.ext
+
+import android.content.Context
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.isVisible
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.ui.widgets.behavior.EngineViewClippingBehavior
+import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior
+import org.mozilla.focus.R
+import mozilla.components.ui.widgets.behavior.ToolbarPosition as engineToolbarPosition
+import mozilla.components.ui.widgets.behavior.ViewPosition as browserToolbarPosition
+
+/**
+ * Collapse the toolbar and block it from appearing until calling [enableDynamicBehavior].
+ * Useful in situations like entering fullscreen.
+ *
+ * @param engineView [EngineView] previously set to react to toolbar's dynamic behavior.
+ * Will now go through a bit of cleanup to ensure everything will be displayed nicely even without a toolbar.
+ */
+fun BrowserToolbar.disableDynamicBehavior(engineView: EngineView) {
+ (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = null
+
+ engineView.setDynamicToolbarMaxHeight(0)
+ engineView.asView().translationY = 0f
+ (engineView.asView().layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = null
+}
+
+/**
+ * Expand the toolbar and reenable the dynamic behavior.
+ * Useful after [disableDynamicBehavior] for situations like exiting fullscreen.
+ *
+ * @param context [Context] used in setting up the dynamic behavior.
+ * @param engineView [EngineView] that should react to toolbar's dynamic behavior.
+ */
+fun BrowserToolbar.enableDynamicBehavior(context: Context, engineView: EngineView) {
+ (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = EngineViewScrollingBehavior(
+ context,
+ null,
+ browserToolbarPosition.TOP,
+ )
+
+ val toolbarHeight = context.resources.getDimension(R.dimen.browser_toolbar_height).toInt()
+ engineView.setDynamicToolbarMaxHeight(toolbarHeight)
+ (engineView.asView().layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
+ topMargin = 0
+ behavior = EngineViewClippingBehavior(
+ context,
+ null,
+ engineView.asView(),
+ toolbarHeight,
+ engineToolbarPosition.TOP,
+ )
+ }
+}
+
+/**
+ * Show this toolbar at the top of the screen, fixed in place, with the EngineView immediately below it.
+ *
+ * @param context [Context] used for various system interactions
+ * @param engineView [EngineView] that must be shown immediately below the toolbar.
+ */
+fun BrowserToolbar.showAsFixed(context: Context, engineView: EngineView) {
+ isVisible = true
+
+ engineView.setDynamicToolbarMaxHeight(0)
+
+ val toolbarHeight = context.resources.getDimension(R.dimen.browser_toolbar_height).toInt()
+ (engineView.asView().layoutParams as? CoordinatorLayout.LayoutParams)?.topMargin = toolbarHeight
+}
+
+/**
+ * Remove this toolbar from the screen and allow the EngineView to occupy the entire screen.
+ *
+ * @param engineView [EngineView] that will be configured to occupy the entire screen.
+ */
+fun BrowserToolbar.hide(engineView: EngineView) {
+ engineView.setDynamicToolbarMaxHeight(0)
+ (engineView.asView().layoutParams as? CoordinatorLayout.LayoutParams)?.topMargin = 0
+
+ isVisible = false
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/ContentState.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/ContentState.kt
new file mode 100644
index 0000000000..86ecc538b0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/ContentState.kt
@@ -0,0 +1,16 @@
+/* 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.focus.ext
+
+import mozilla.components.browser.state.state.ContentState
+
+val ContentState.hasSearchTerms: Boolean
+ get() = searchTerms.isNotEmpty()
+
+/**
+ * Returns the tab site title or domain name if title is empty.
+ */
+val ContentState.titleOrDomain: String
+ get() = title.ifEmpty { url.tryGetRootDomain }
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Context.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Context.kt
new file mode 100644
index 0000000000..e4b8f89bb3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Context.kt
@@ -0,0 +1,60 @@
+/* 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.focus.ext
+
+import android.app.Activity
+import android.content.Context
+import android.view.ContextThemeWrapper
+import android.view.accessibility.AccessibilityManager
+import mozilla.components.support.utils.ext.getPackageInfoCompat
+import org.mozilla.focus.Components
+import org.mozilla.focus.FocusApplication
+import org.mozilla.focus.utils.Settings
+import org.mozilla.gecko.util.HardwareUtils
+import java.text.DateFormat
+
+/**
+ * Get the FocusApplication object from a context.
+ */
+val Context.application: FocusApplication
+ get() = applicationContext as FocusApplication
+
+/**
+ * Get the components of this application.
+ */
+val Context.components: Components
+ get() = application.components
+
+/**
+ * Get the settings of this application.
+ */
+val Context.settings: Settings
+ get() = application.components.settings
+
+/**
+ * System's [AccessibilityManager].
+ */
+val Context.accessibilityManager: AccessibilityManager
+ get() = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+
+/**
+ * Get the app install date.
+ */
+val Context.installedDate: String
+ get() {
+ val installTime = this.packageManager.getPackageInfoCompat(this.packageName, 0).firstInstallTime
+ return DateFormat.getDateInstance().format(installTime)
+ }
+
+/**
+ * Checks if the current device is a tablet.
+ */
+fun Context.isTablet(): Boolean = HardwareUtils.isTablet(this)
+
+/**
+ * Casts [Context] to [Activity].
+ */
+fun Context.tryAsActivity() =
+ (this as? ContextThemeWrapper)?.baseContext as? Activity ?: this as? Activity
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Fragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Fragment.kt
new file mode 100644
index 0000000000..e0694eb0be
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Fragment.kt
@@ -0,0 +1,47 @@
+/* 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.focus.ext
+
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import org.mozilla.focus.Components
+import org.mozilla.focus.activity.MainActivity
+
+/**
+ * Get the components of this application or null if this Fragment is not attached to a Context.
+ */
+val Fragment.components: Components?
+ get() = context?.components
+
+/**
+ * Get the components of this application.
+ *
+ * This method will throw an exception if this Fragment is not attached to a Context.
+ */
+val Fragment.requireComponents: Components
+ get() = requireContext().components
+
+/**
+ * Get the preference key.
+ * @param preferenceId Resource ID from preference_keys
+ */
+fun Fragment.getPreferenceKey(@StringRes preferenceId: Int): String = getString(preferenceId)
+
+/**
+ * Displays the toolbar with the given [title] if the parent activity
+ * can be casted to [AppCompatActivity] and [MainActivity]
+ */
+fun Fragment.showToolbar(title: String) {
+ (requireActivity() as? AppCompatActivity)?.title = title
+ (requireActivity() as? MainActivity)?.getToolbar()?.show()
+}
+
+/**
+ * Hides the activity toolbar if the fragment is attached to an [AppCompatActivity].
+ */
+fun Fragment.hideToolbar() {
+ (requireActivity() as? AppCompatActivity)?.supportActionBar?.hide()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/PreferenceFragmentCompat.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/PreferenceFragmentCompat.kt
new file mode 100644
index 0000000000..a1349c5bdc
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/PreferenceFragmentCompat.kt
@@ -0,0 +1,16 @@
+/* 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.focus.ext
+
+import androidx.annotation.StringRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+
+/**
+ * Find a preference with the corresponding key and throw if it does not exist.
+ * @param preferenceId Resource ID from preference_keys
+ */
+fun PreferenceFragmentCompat.requirePreference(@StringRes preferenceId: Int) =
+ requireNotNull(findPreference(getPreferenceKey(preferenceId)))
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/SessionState.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/SessionState.kt
new file mode 100644
index 0000000000..f211bc6b45
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/SessionState.kt
@@ -0,0 +1,25 @@
+/* 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.focus.ext
+
+import mozilla.components.browser.state.state.CustomTabSessionState
+import mozilla.components.browser.state.state.SessionState
+
+/**
+ * Returns this [SessionState] cast to [CustomTabSessionState] if possible. Otherwise returns `null`.
+ */
+fun SessionState.ifCustomTab(): CustomTabSessionState? {
+ if (this is CustomTabSessionState) {
+ return this
+ }
+ return null
+}
+
+/**
+ * Returns `true` if this [SessionState] is a custom tab (an instance of [CustomTabSessionState]).
+ */
+fun SessionState.isCustomTab(): Boolean {
+ return this is CustomTabSessionState
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/String.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/String.kt
new file mode 100644
index 0000000000..d0711ab15d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/String.kt
@@ -0,0 +1,75 @@
+/* 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.focus.ext
+
+import androidx.compose.ui.graphics.Color
+import androidx.core.net.toUri
+import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
+import mozilla.components.support.ktx.util.URLStringUtils
+
+// Extension functions for the String class
+
+/**
+ * Beautify a URL by truncating it in a way that highlights important parts of the URL.
+ *
+ * Spec: https://github.com/mozilla-mobile/focus-android/issues/1231#issuecomment-326237077
+ */
+fun String.beautifyUrl(): String {
+ if (isNullOrEmpty() || !URLStringUtils.isHttpOrHttps(this)) {
+ return this
+ }
+
+ val beautifulUrl = StringBuilder()
+
+ val uri = this.toUri()
+
+ // Use only the truncated host name
+
+ val truncatedHost = uri.truncatedHost()
+ if (truncatedHost.isNullOrEmpty()) {
+ return this
+ }
+
+ beautifulUrl.append(truncatedHost)
+
+ // Append the truncated path
+
+ val truncatedPath = uri.truncatedPath()
+ if (truncatedPath.isNotEmpty()) {
+ beautifulUrl.append(truncatedPath)
+ }
+
+ // And then append (only) the first query parameter
+
+ val query = uri.query
+ if (!query.isNullOrEmpty()) {
+ beautifulUrl.append("?")
+ beautifulUrl.append(query.split("&").first())
+ }
+
+ // We always append a fragment if there's one
+
+ val fragment = uri.fragment
+ if (!fragment.isNullOrEmpty()) {
+ beautifulUrl.append("#")
+ beautifulUrl.append(fragment)
+ }
+
+ return beautifulUrl.toString()
+}
+
+/**
+ * Tries to parse and get root domain part if [String] is a valid URL.
+ */
+val String.tryGetRootDomain: String
+ get() =
+ this.toUri().hostWithoutCommonPrefixes?.replaceAfter(".", "")?.removeSuffix(".")
+ ?.replaceFirstChar { it.uppercase() } ?: this
+
+/**
+ * Tries to parse a color string and return a [Color]
+ */
+val String.color: Color
+ get() = Color(android.graphics.Color.parseColor(this))
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Uri.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Uri.kt
new file mode 100644
index 0000000000..b13857f84c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ext/Uri.kt
@@ -0,0 +1,69 @@
+/* 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.focus.ext
+
+import android.net.Uri
+import mozilla.components.support.ktx.kotlin.ELLIPSIS
+
+// Extension functions for the android.net.Uri class
+
+/**
+ * Return the truncated host of this Uri. The truncated host will only contain up to 2-3 segments of
+ * the original host. The original host will be returned if it's null or and empty String.
+ *
+ * Examples:
+ * mail.google.com -> google.com
+ * www.tomshardware.co.uk -> tomshardware.co.uk
+ *
+ * Spec: https://github.com/mozilla-mobile/focus-android/issues/1231#issuecomment-326237077
+ */
+fun Uri.truncatedHost(): String? {
+ if (host.isNullOrEmpty()) {
+ return host
+ }
+
+ // We start with the host name:
+ // * Pick the last two segments:
+ // en.m.wikipedia.org -> wikipedia.org
+ // * If the last element has a length of 2 or less than take another segment:
+ // www.tomshardware.co.uk -> tomshardware.co.uk
+ // * If there's no host then just remove the URL as is.
+
+ val hostSegments = host!!.split(".")
+ val usedHostSegments = mutableListOf()
+
+ for (segment in hostSegments.reversed()) {
+ if (usedHostSegments.size < 2) {
+ // We always pick the first two segments
+ usedHostSegments.add(0, segment)
+ } else if (usedHostSegments.first().length <= 2) {
+ // The last segment has a length of 2 or less -> pick this segment too
+ usedHostSegments.add(0, segment)
+ } else {
+ // We have everything we need. Bail out.
+ break
+ }
+ }
+
+ return usedHostSegments.joinToString(separator = ".")
+}
+
+/**
+ * Return the truncated path only containing the first and last segment of the full path. If the
+ * Uri does not have a path an empty String will be returned.
+ *
+ * Example:
+ * /foo/bar/test/index.html -> /foo/…/index.html
+ */
+fun Uri.truncatedPath(): String {
+ val segments = pathSegments
+
+ return when (segments.size) {
+ 0 -> ""
+ 1 -> "/" + segments[0]
+ 2 -> "/" + segments.joinToString(separator = "/")
+ else -> "/" + listOf(segments.first(), Char.ELLIPSIS, segments.last()).joinToString(separator = "/")
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunCardView.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunCardView.kt
new file mode 100644
index 0000000000..eea14dc1ff
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunCardView.kt
@@ -0,0 +1,38 @@
+/* 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.focus.firstrun
+
+import android.content.Context
+import android.util.AttributeSet
+import org.mozilla.focus.R
+import kotlin.math.min
+
+class FirstrunCardView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = R.attr.cardViewStyle,
+) : androidx.cardview.widget.CardView(context, attrs, defStyleAttr) {
+
+ private val maxWidth = resources.getDimensionPixelSize(R.dimen.firstrun_card_width)
+ private val maxHeight = resources.getDimensionPixelSize(R.dimen.firstrun_card_height)
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ // The view is set to match_parent in the layout file. So width and height should be the
+ // value needed to fill the whole parent view.
+ val availableWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val availableHeight = MeasureSpec.getSize(heightMeasureSpec)
+
+ // Now let's use those sizes to measure - but let's not exceed our defined max sizes (We do
+ // not want to have gigantic cards on large devices like tablets.)
+ val measuredWidth = min(availableWidth, maxWidth)
+ val measuredHeight = min(availableHeight, maxHeight)
+
+ // Let's use the measured values to hand them to the super class to measure the child views etc.
+ val newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY)
+ val newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
+
+ super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunPagerAdapter.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunPagerAdapter.kt
new file mode 100644
index 0000000000..e3a60d271c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/firstrun/FirstrunPagerAdapter.kt
@@ -0,0 +1,103 @@
+/* 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.focus.firstrun
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.viewpager.widget.PagerAdapter
+import androidx.viewpager.widget.ViewPager
+import org.mozilla.focus.R
+
+class FirstrunPagerAdapter(
+ private val context: Context,
+ private val listener: View.OnClickListener,
+) : PagerAdapter() {
+
+ private data class FirstrunPage(val title: String, val text: String, val imageResource: Int) {
+ val contentDescription = title + text
+ }
+
+ private val pages: Array
+
+ init {
+ val appName = context.getString(R.string.app_name)
+ pages = arrayOf(
+ FirstrunPage(
+ context.getString(R.string.firstrun_defaultbrowser_title),
+ context.getString(R.string.firstrun_defaultbrowser_text2),
+ R.drawable.onboarding_img1,
+ ),
+ FirstrunPage(
+ context.getString(R.string.firstrun_search_title),
+ context.getString(R.string.firstrun_search_text),
+ R.drawable.onboarding_img4,
+ ),
+ FirstrunPage(
+ context.getString(R.string.firstrun_shortcut_title),
+ context.getString(R.string.firstrun_shortcut_text, appName),
+ R.drawable.onboarding_img3,
+ ),
+ FirstrunPage(
+ context.getString(R.string.firstrun_privacy_title),
+ context.getString(R.string.firstrun_privacy_text, appName),
+ R.drawable.onboarding_img2,
+ ),
+ )
+ }
+
+ private fun getView(position: Int, pager: ViewPager): View {
+ val view = LayoutInflater.from(context).inflate(R.layout.firstrun_page, pager, false)
+
+ val page = pages[position]
+
+ val titleView: TextView = view.findViewById(R.id.title)
+ titleView.text = page.title
+
+ val textView: TextView = view.findViewById(R.id.text)
+ textView.text = page.text
+
+ val imageView: ImageView = view.findViewById(R.id.image)
+ imageView.setImageResource(page.imageResource)
+
+ val buttonView: Button = view.findViewById(R.id.button)
+ buttonView.setOnClickListener(listener)
+ if (position == pages.size - 1) {
+ buttonView.setText(R.string.firstrun_close_button)
+ buttonView.id = R.id.finish
+ buttonView.contentDescription = buttonView.text.toString().lowercase()
+ } else {
+ buttonView.setText(R.string.firstrun_next_button)
+ buttonView.id = R.id.next
+ }
+
+ return view
+ }
+
+ fun getPageAccessibilityDescription(position: Int): String =
+ pages[position].contentDescription
+
+ override fun isViewFromObject(view: View, any: Any) = view === any
+
+ override fun getCount() = pages.size
+
+ override fun instantiateItem(container: ViewGroup, position: Int): Any {
+ container as ViewPager
+
+ val view = getView(position, container)
+ container.addView(view)
+
+ return view
+ }
+
+ override fun destroyItem(container: ViewGroup, position: Int, view: Any) {
+ view as View
+ container.removeView(view)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/AddToHomescreenDialogFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/AddToHomescreenDialogFragment.kt
new file mode 100644
index 0000000000..f06d789299
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/AddToHomescreenDialogFragment.kt
@@ -0,0 +1,145 @@
+/* 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.focus.fragment
+
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ImageView
+import androidx.appcompat.app.AlertDialog
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+import mozilla.components.browser.icons.IconRequest
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.AddToHomeScreen
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.shortcut.HomeScreen
+import org.mozilla.focus.shortcut.IconGenerator
+
+/**
+ * Fragment displaying a dialog where a user can change the title for a homescreen shortcut
+ */
+class AddToHomescreenDialogFragment : DialogFragment() {
+
+ @Suppress("LongMethod")
+ override fun onCreateDialog(bundle: Bundle?): AlertDialog {
+ AddToHomeScreen.dialogDisplayed.record(NoExtras())
+ val url = requireArguments().getString(URL)!!
+ val title = requireArguments().getString(TITLE)
+ val blockingEnabled = requireArguments().getBoolean(BLOCKING_ENABLED)
+ val requestDesktop = requireArguments().getBoolean(REQUEST_DESKTOP)
+
+ val builder = AlertDialog.Builder(requireActivity(), R.style.DialogStyle)
+ builder.setCancelable(true)
+ val inflater = requireActivity().layoutInflater
+ val dialogView = inflater.inflate(R.layout.dialog_add_to_homescreen2, null)
+ builder.setView(dialogView)
+
+ val iconView = dialogView.findViewById(R.id.homescreen_icon)
+ requireContext().components.icons.loadIntoView(
+ iconView,
+ IconRequest(url, isPrivate = true),
+ )
+
+ val blockIcon = dialogView.findViewById(R.id.homescreen_dialog_block_icon)
+ blockIcon.setImageResource(R.drawable.mozac_ic_shield_slash_24)
+ val warning =
+ dialogView.findViewById(R.id.homescreen_dialog_warning_layout)
+ warning.isVisible = !blockingEnabled
+
+ val editableTitle = dialogView.findViewById(R.id.edit_title)
+
+ if (!TextUtils.isEmpty(title)) {
+ editableTitle.setText(title)
+ }
+
+ setButtons(dialogView, editableTitle, url, blockingEnabled, requestDesktop, title)
+
+ return builder.create()
+ }
+
+ @Suppress("LongParameterList")
+ private fun setButtons(
+ parentView: View,
+ editableTitle: EditText,
+ iconUrl: String,
+ blockingEnabled: Boolean,
+ requestDesktop: Boolean,
+ initialTitle: String?,
+ ) {
+ val addToHomescreenDialogCancelButton =
+ parentView.findViewById(R.id.addtohomescreen_dialog_cancel)
+ val addToHomescreenDialogConfirmButton =
+ parentView.findViewById(R.id.addtohomescreen_dialog_add)
+
+ addToHomescreenDialogCancelButton.setOnClickListener {
+ AddToHomeScreen.cancelButtonTapped.record(NoExtras())
+
+ dismiss()
+ }
+
+ addToHomescreenDialogConfirmButton.setOnClickListener {
+ HomeScreen.installShortCut(
+ requireContext(),
+ IconGenerator.generateLauncherIcon(requireContext(), iconUrl),
+ iconUrl,
+ editableTitle.text.toString().trim { it <= ' ' },
+ blockingEnabled,
+ requestDesktop,
+ )
+
+ val hasEditedTitle = initialTitle != editableTitle.text.toString().trim { it <= ' ' }
+ AddToHomeScreen.addButtonTapped.record(
+ AddToHomeScreen.AddButtonTappedExtra(
+ hasEditedTitle = hasEditedTitle,
+ ),
+ )
+
+ PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
+ .putBoolean(
+ requireContext().getString(R.string.has_added_to_home_screen),
+ true,
+ ).apply()
+ dismiss()
+ }
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+
+ companion object {
+ const val FRAGMENT_TAG = "add-to-homescreen-prompt-dialog"
+ private const val URL = "url"
+ private const val TITLE = "title"
+ private const val BLOCKING_ENABLED = "blocking_enabled"
+ private const val REQUEST_DESKTOP = "request_desktop"
+
+ fun newInstance(
+ url: String,
+ title: String,
+ blockingEnabled: Boolean,
+ requestDesktop: Boolean,
+ ): AddToHomescreenDialogFragment {
+ val frag = AddToHomescreenDialogFragment()
+ val args = Bundle()
+ args.putString(URL, url)
+ args.putString(TITLE, title)
+ args.putBoolean(BLOCKING_ENABLED, blockingEnabled)
+ args.putBoolean(REQUEST_DESKTOP, requestDesktop)
+ frag.arguments = args
+ return frag
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BaseFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BaseFragment.kt
new file mode 100644
index 0000000000..90e357f039
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BaseFragment.kt
@@ -0,0 +1,57 @@
+/* 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.focus.fragment
+
+import android.content.res.Resources
+import android.view.animation.Animation
+import android.view.animation.AnimationSet
+import android.view.animation.AnimationUtils
+import androidx.core.view.isInvisible
+import androidx.fragment.app.Fragment
+import org.mozilla.focus.ext.hideToolbar
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.state.Screen
+
+abstract class BaseFragment : Fragment() {
+ private var animationSet: AnimationSet? = null
+
+ override fun onResume() {
+ super.onResume()
+ hideToolbar()
+ view?.isInvisible = requireComponents.appStore.state.screen == Screen.Locked()
+ }
+
+ fun cancelAnimation() {
+ if (animationSet != null) {
+ animationSet!!.duration = 0
+ animationSet!!.cancel()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ view?.isInvisible = requireComponents.appStore.state.screen == Screen.Locked()
+ }
+
+ @Suppress("SwallowedException")
+ override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
+ var animation = super.onCreateAnimation(transit, enter, nextAnim)
+ if (animation == null && nextAnim != 0) {
+ animation = try {
+ AnimationUtils.loadAnimation(activity, nextAnim)
+ } catch (e: Resources.NotFoundException) {
+ return null
+ }
+ }
+ return if (animation != null) {
+ val animSet = AnimationSet(true)
+ animSet.addAnimation(animation)
+ animationSet = animSet
+ animSet
+ } else {
+ null
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
new file mode 100644
index 0000000000..a25f26ef85
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
@@ -0,0 +1,1072 @@
+/* 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.focus.fragment
+
+import android.annotation.SuppressLint
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityManager
+import android.webkit.MimeTypeMap
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.net.toUri
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceManager
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
+import mozilla.components.browser.state.selector.findTabOrCustomTab
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.state.CustomTabConfig
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.state.content.DownloadState
+import mozilla.components.browser.state.state.createTab
+import mozilla.components.concept.engine.HitResult
+import mozilla.components.feature.app.links.AppLinksFeature
+import mozilla.components.feature.contextmenu.ContextMenuFeature
+import mozilla.components.feature.downloads.AbstractFetchDownloadService
+import mozilla.components.feature.downloads.DownloadsFeature
+import mozilla.components.feature.downloads.manager.FetchDownloadManager
+import mozilla.components.feature.downloads.temporary.ShareDownloadFeature
+import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
+import mozilla.components.feature.prompts.PromptFeature
+import mozilla.components.feature.session.PictureInPictureFeature
+import mozilla.components.feature.session.SessionFeature
+import mozilla.components.feature.sitepermissions.SitePermissionsFeature
+import mozilla.components.feature.tabs.WindowFeature
+import mozilla.components.feature.top.sites.TopSitesConfig
+import mozilla.components.feature.top.sites.TopSitesFeature
+import mozilla.components.lib.crash.Crash
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.base.feature.UserInteractionHandler
+import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import mozilla.components.support.ktx.android.view.exitImmersiveMode
+import mozilla.components.support.locale.ActivityContextWrapper
+import mozilla.components.support.utils.Browsers
+import mozilla.components.support.utils.StatusBarUtils
+import mozilla.components.support.utils.ext.requestInPlacePermissions
+import org.mozilla.focus.GleanMetrics.Browser
+import org.mozilla.focus.GleanMetrics.CookieBanner
+import org.mozilla.focus.GleanMetrics.Downloads
+import org.mozilla.focus.GleanMetrics.OpenWith
+import org.mozilla.focus.GleanMetrics.TabCount
+import org.mozilla.focus.GleanMetrics.TrackingProtection
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.InstallFirefoxActivity
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.browser.integration.BrowserMenuController
+import org.mozilla.focus.browser.integration.BrowserToolbarIntegration
+import org.mozilla.focus.browser.integration.FindInPageIntegration
+import org.mozilla.focus.browser.integration.FullScreenIntegration
+import org.mozilla.focus.contextmenu.ContextMenuCandidates
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerAction
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerDetailsPanel
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerMiddleware
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerState
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerStore
+import org.mozilla.focus.cookiebannerreducer.DefaultCookieBannerReducerInteractor
+import org.mozilla.focus.databinding.FragmentBrowserBinding
+import org.mozilla.focus.downloads.DownloadService
+import org.mozilla.focus.engine.EngineSharedPreferencesListener
+import org.mozilla.focus.ext.accessibilityManager
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.disableDynamicBehavior
+import org.mozilla.focus.ext.enableDynamicBehavior
+import org.mozilla.focus.ext.ifCustomTab
+import org.mozilla.focus.ext.isCustomTab
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.showAsFixed
+import org.mozilla.focus.ext.titleOrDomain
+import org.mozilla.focus.menu.browser.DefaultBrowserMenu
+import org.mozilla.focus.open.OpenWithFragment
+import org.mozilla.focus.session.ui.TabsPopup
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsStorage
+import org.mozilla.focus.settings.privacy.ConnectionDetailsPanel
+import org.mozilla.focus.settings.privacy.TrackingProtectionPanel
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.topsites.DefaultTopSitesStorage.Companion.TOP_SITES_MAX_LIMIT
+import org.mozilla.focus.topsites.DefaultTopSitesView
+import org.mozilla.focus.utils.FocusSnackbar
+import org.mozilla.focus.utils.FocusSnackbarDelegate
+import org.mozilla.focus.utils.IntentUtils
+import org.mozilla.focus.utils.ViewUtils
+import java.net.URLEncoder
+
+/**
+ * Fragment for displaying the browser UI.
+ */
+@Suppress("LargeClass", "TooManyFunctions")
+class BrowserFragment :
+ BaseFragment(),
+ UserInteractionHandler,
+ AccessibilityManager.AccessibilityStateChangeListener {
+
+ private var _binding: FragmentBrowserBinding? = null
+ private val binding get() = _binding!!
+
+ private val findInPageIntegration = ViewBoundFeatureWrapper()
+ private val fullScreenIntegration = ViewBoundFeatureWrapper()
+ private var pictureInPictureFeature: PictureInPictureFeature? = null
+
+ internal val sessionFeature = ViewBoundFeatureWrapper()
+ private val promptFeature = ViewBoundFeatureWrapper()
+ private val contextMenuFeature = ViewBoundFeatureWrapper()
+ private val downloadsFeature = ViewBoundFeatureWrapper()
+ private val shareDownloadFeature = ViewBoundFeatureWrapper()
+ private val windowFeature = ViewBoundFeatureWrapper()
+ private val appLinksFeature = ViewBoundFeatureWrapper()
+ private val topSitesFeature = ViewBoundFeatureWrapper()
+ private var sitePermissionsFeature = ViewBoundFeatureWrapper()
+ private var fullScreenMediaSessionFeature =
+ ViewBoundFeatureWrapper()
+
+ private val toolbarIntegration = ViewBoundFeatureWrapper()
+
+ private var trackingProtectionPanel: TrackingProtectionPanel? = null
+ private lateinit var requestPermissionLauncher: ActivityResultLauncher>
+ private lateinit var cookieBannerReducerStore: CookieBannerReducerStore
+ private lateinit var defaultCookieBannerInteractor: DefaultCookieBannerReducerInteractor
+ private var tabsPopup: TabsPopup? = null
+ private var siteNotSupportedSnackBarScope: CoroutineScope? = null
+
+ /**
+ * The ID of the tab associated with this fragment.
+ */
+ private val tabId: String
+ get() = requireArguments().getString(ARGUMENT_SESSION_UUID)
+ ?: throw IllegalAccessError("No session ID set on fragment")
+
+ /**
+ * The tab associated with this fragment.
+ */
+ val tab: SessionState
+ get() = requireComponents.store.state.findTabOrCustomTab(tabId)
+ // Workaround for tab not existing temporarily.
+ ?: createTab("about:blank")
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ requestPermissionLauncher =
+ registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions(),
+ ) { permissionsResult ->
+ val grandResults = ArrayList()
+ permissionsResult.entries.forEach {
+ val isGranted = it.value
+ if (isGranted) {
+ grandResults.add(PackageManager.PERMISSION_GRANTED)
+ } else {
+ grandResults.add(PackageManager.PERMISSION_DENIED)
+ }
+ }
+ val feature = sitePermissionsFeature.get()
+ feature?.onPermissionsResult(
+ permissionsResult.keys.toTypedArray(),
+ grandResults.toIntArray(),
+ )
+ }
+ }
+
+ /**
+ * Initialize CookieBannerStore ,Interactor and report site snackBar
+ * when tacking protection panel is shown.
+ */
+ fun initCookieBanner() {
+ cookieBannerReducerStore = CookieBannerReducerStore(
+ CookieBannerReducerState(),
+ listOf(
+ CookieBannerReducerMiddleware(
+ ioScope = this.lifecycleScope + Dispatchers.IO,
+ cookieBannersStorage = requireContext().components.cookieBannerStorage,
+ appContext = requireContext(),
+ currentTab = tab,
+ ),
+ ),
+ )
+ defaultCookieBannerInteractor = DefaultCookieBannerReducerInteractor(
+ store = cookieBannerReducerStore,
+ )
+ updateCookieBannerSiteToReportSnackBar()
+ }
+
+ private fun updateCookieBannerSiteToReportSnackBar() {
+ siteNotSupportedSnackBarScope = cookieBannerReducerStore.flowScoped { flow ->
+ flow.mapNotNull { state -> state.showSnackBarForSiteToReport }
+ .distinctUntilChanged()
+ .collect { showSnackBarForSiteToReport ->
+ if (showSnackBarForSiteToReport) {
+ ViewUtils.showBrandedSnackbar(
+ view,
+ R.string.cookie_banner_report_a_site_snackbar_label,
+ 0,
+ )
+ cookieBannerReducerStore.dispatch(
+ CookieBannerReducerAction.ShowSnackBarForSiteToReport(
+ false,
+ ),
+ )
+ }
+ }
+ }
+ }
+
+ @Suppress("LongMethod", "ComplexMethod")
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ _binding = FragmentBrowserBinding.inflate(inflater, container, false)
+
+ requireContext().accessibilityManager.addAccessibilityStateChangeListener(this)
+
+ val originalContext = ActivityContextWrapper.getOriginalContext(requireActivity())
+ binding.engineView.setActivityContext(originalContext)
+
+ return binding.root
+ }
+
+ @SuppressLint("VisibleForTests")
+ @Suppress("ComplexCondition", "LongMethod")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ val components = requireComponents
+
+ findInPageIntegration.set(
+ FindInPageIntegration(
+ components.store,
+ binding.findInPage,
+ binding.browserToolbar,
+ binding.engineView,
+ ),
+ this,
+ view,
+ )
+
+ fullScreenIntegration.set(
+ FullScreenIntegration(
+ requireActivity(),
+ components.store,
+ tab.id,
+ components.sessionUseCases,
+ requireContext().settings,
+ binding.browserToolbar,
+ binding.statusBarBackground,
+ binding.engineView,
+ parentFragmentManager,
+ ),
+ this,
+ view,
+ )
+
+ pictureInPictureFeature = PictureInPictureFeature(
+ store = components.store,
+ activity = requireActivity(),
+ crashReporting = components.crashReporter,
+ tabId = tabId,
+ )
+
+ contextMenuFeature.set(
+ ContextMenuFeature(
+ parentFragmentManager,
+ components.store,
+ ContextMenuCandidates.get(
+ requireContext(),
+ components.tabsUseCases,
+ components.contextMenuUseCases,
+ components.appLinksUseCases,
+ view,
+ FocusSnackbarDelegate(view),
+ tab.isCustomTab(),
+ ),
+ binding.engineView,
+ requireComponents.contextMenuUseCases,
+ tabId,
+ additionalNote = { hitResult -> getAdditionalNote(hitResult) },
+ ),
+ this,
+ view,
+ )
+
+ sessionFeature.set(
+ SessionFeature(
+ components.store,
+ components.sessionUseCases.goBack,
+ binding.engineView,
+ tab.id,
+ ),
+ this,
+ view,
+ )
+
+ promptFeature.set(
+ PromptFeature(
+ fragment = this,
+ store = components.store,
+ tabsUseCases = components.tabsUseCases,
+ customTabId = tryGetCustomTabId(),
+ fragmentManager = parentFragmentManager,
+ fileUploadsDirCleaner = requireComponents.fileUploadsDirCleaner,
+ onNeedToRequestPermissions = { permissions ->
+ requestInPlacePermissions(REQUEST_KEY_PROMPT_PERMISSIONS, permissions) { result ->
+ promptFeature.get()?.onPermissionsResult(
+ result.keys.toTypedArray(),
+ result.values.map {
+ when (it) {
+ true -> PackageManager.PERMISSION_GRANTED
+ false -> PackageManager.PERMISSION_DENIED
+ }
+ }.toIntArray(),
+ )
+ }
+ },
+ ),
+ this,
+ view,
+ )
+
+ downloadsFeature.set(
+ DownloadsFeature(
+ requireContext().applicationContext,
+ components.store,
+ components.downloadsUseCases,
+ fragmentManager = childFragmentManager,
+ tabId = tabId,
+ downloadManager = FetchDownloadManager(
+ requireContext().applicationContext,
+ components.store,
+ DownloadService::class,
+ notificationsDelegate = components.notificationsDelegate,
+ ),
+ onNeedToRequestPermissions = { permissions ->
+ requestInPlacePermissions(REQUEST_KEY_DOWNLOAD_PERMISSIONS, permissions) { result ->
+ downloadsFeature.get()?.onPermissionsResult(
+ result.keys.toTypedArray(),
+ result.values.map {
+ when (it) {
+ true -> PackageManager.PERMISSION_GRANTED
+ false -> PackageManager.PERMISSION_DENIED
+ }
+ }.toIntArray(),
+ )
+ }
+ },
+ onDownloadStopped = { state, _, status ->
+ handleDownloadStopped(state, status)
+ },
+ ),
+ this,
+ view,
+ )
+
+ shareDownloadFeature.set(
+ ShareDownloadFeature(
+ context = requireContext().applicationContext,
+ httpClient = components.client,
+ store = components.store,
+ tabId = tab.id,
+ ),
+ this,
+ view,
+ )
+
+ appLinksFeature.set(
+ feature = AppLinksFeature(
+ requireContext(),
+ store = components.store,
+ sessionId = tabId,
+ fragmentManager = parentFragmentManager,
+ launchInApp = { requireContext().settings.openLinksInExternalApp },
+ loadUrlUseCase = requireContext().components.sessionUseCases.loadUrl,
+ failedToLaunchAction = { fallbackUrl ->
+ fallbackUrl?.let {
+ val appLinksUseCases = components.appLinksUseCases
+ val getRedirect = appLinksUseCases.appLinkRedirect
+ val redirect = getRedirect.invoke(fallbackUrl)
+ redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ appLinksUseCases.openAppLink.invoke(redirect.appIntent)
+ }
+ },
+ ),
+ owner = this,
+ view = view,
+ )
+
+ topSitesFeature.set(
+ feature = TopSitesFeature(
+ view = DefaultTopSitesView(requireComponents.appStore),
+ storage = requireComponents.topSitesStorage,
+ config = {
+ TopSitesConfig(
+ totalSites = TOP_SITES_MAX_LIMIT,
+ frecencyConfig = null,
+ providerConfig = null,
+ )
+ },
+ ),
+ owner = this,
+ view = view,
+ )
+
+ customizeToolbar()
+
+ val customTabConfig = tab.ifCustomTab()?.config
+ if (customTabConfig != null) {
+ initialiseCustomTabUi(customTabConfig)
+
+ // TODO Add custom tabs window feature support
+ // We to add support for Custom Tabs here, however in order to send the window request
+ // back to us through the intent system, we need to register a unique schema that we
+ // can handle. For example, Fenix Nighlyt does this today with `fenix-nightly://`.
+ } else {
+ initialiseNormalBrowserUi()
+
+ windowFeature.set(
+ feature = WindowFeature(
+ store = components.store,
+ tabsUseCases = components.tabsUseCases,
+ ),
+ owner = this,
+ view = view,
+ )
+ }
+
+ // Feature that handles MediaSession state changes
+ fullScreenMediaSessionFeature.set(
+ feature = MediaSessionFullscreenFeature(requireActivity(), requireComponents.store, tryGetCustomTabId()),
+ owner = this,
+ view = view,
+ )
+
+ setSitePermissions(view)
+ }
+
+ private fun setSitePermissions(rootView: View) {
+ sitePermissionsFeature.set(
+ feature = SitePermissionsFeature(
+ context = requireContext(),
+ fragmentManager = parentFragmentManager,
+ onNeedToRequestPermissions = { permissions ->
+ if (SitePermissionOptionsStorage(requireContext()).isSitePermissionNotBlocked(permissions)) {
+ requestPermissionLauncher.launch(permissions)
+ }
+ },
+ onShouldShowRequestPermissionRationale = {
+ // Since we don't request permissions this it will not be called
+ false
+ },
+ sitePermissionsRules = SitePermissionOptionsStorage(requireContext()).getSitePermissionsSettingsRules(),
+ sessionId = tabId,
+ store = requireComponents.store,
+ shouldShowDoNotAskAgainCheckBox = false,
+ ),
+ owner = this,
+ view = rootView,
+ )
+ if (requireComponents.appStore.state.sitePermissionOptionChange) {
+ requireComponents.sessionUseCases.reload(tabId)
+ requireComponents.appStore.dispatch(AppAction.SitePermissionOptionChange(false))
+ }
+ }
+
+ override fun onAccessibilityStateChanged(enabled: Boolean) {
+ when (enabled) {
+ // using _binding, because this might be called before onCreateView.
+ false -> _binding?.browserToolbar?.enableDynamicBehavior(
+ requireContext(),
+ binding.engineView,
+ )
+ true -> {
+ _binding?.browserToolbar?.let {
+ it.disableDynamicBehavior(binding.engineView)
+ it.showAsFixed(requireContext(), binding.engineView)
+ }
+ }
+ }
+ }
+
+ override fun onPictureInPictureModeChanged(enabled: Boolean) {
+ pictureInPictureFeature?.onPictureInPictureModeChanged(enabled)
+ if (lifecycle.currentState == Lifecycle.State.CREATED) {
+ onBackPressed()
+ }
+ }
+
+ private fun getAdditionalNote(hitResult: HitResult): String? {
+ return if ((hitResult is HitResult.IMAGE_SRC || hitResult is HitResult.IMAGE) &&
+ hitResult.src.isNotEmpty()
+ ) {
+ getString(R.string.contextmenu_erased_images_note2, getString(R.string.app_name))
+ } else {
+ null
+ }
+ }
+
+ private fun customizeToolbar() {
+ val controller = BrowserMenuController(
+ requireComponents.sessionUseCases,
+ requireComponents.appStore,
+ requireComponents.store,
+ requireComponents.topSitesUseCases,
+ tabId,
+ ::shareCurrentUrl,
+ ::setShouldRequestDesktop,
+ ::showAddToHomescreenDialog,
+ ::showFindInPageBar,
+ ::openSelectBrowser,
+ ::openInBrowser,
+ ::showShortcutAddedSnackBar,
+ )
+
+ if (tab.ifCustomTab()?.config == null) {
+ val browserMenu = DefaultBrowserMenu(
+ context = requireContext(),
+ appStore = requireComponents.appStore,
+ store = requireComponents.store,
+ isPinningSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(
+ requireContext(),
+ ),
+ onItemTapped = { controller.handleMenuInteraction(it) },
+ )
+ binding.browserToolbar.display.menuBuilder = browserMenu.menuBuilder
+ }
+
+ toolbarIntegration.set(
+ BrowserToolbarIntegration(
+ requireComponents.store,
+ toolbar = binding.browserToolbar,
+ fragment = this,
+ controller = controller,
+ customTabId = tryGetCustomTabId(),
+ customTabsUseCases = requireComponents.customTabsUseCases,
+ sessionUseCases = requireComponents.sessionUseCases,
+ onUrlLongClicked = ::onUrlLongClicked,
+ eraseActionListener = { erase(shouldEraseAllTabs = true) },
+ tabCounterListener = ::tabCounterListener,
+ ),
+ owner = this,
+ view = binding.browserToolbar,
+ )
+ }
+
+ private fun showShortcutAddedSnackBar() {
+ FocusSnackbar.make(requireView())
+ .setText(requireContext().getString(R.string.snackbar_added_to_shortcuts))
+ .show()
+ }
+
+ private fun initialiseNormalBrowserUi() {
+ if (!requireContext().settings.isAccessibilityEnabled()) {
+ binding.browserToolbar.enableDynamicBehavior(requireContext(), binding.engineView)
+ } else {
+ binding.browserToolbar.showAsFixed(requireContext(), binding.engineView)
+ }
+ }
+
+ private fun initialiseCustomTabUi(customTabConfig: CustomTabConfig) {
+ if (customTabConfig.enableUrlbarHiding && !requireContext().settings.isAccessibilityEnabled()) {
+ binding.browserToolbar.enableDynamicBehavior(requireContext(), binding.engineView)
+ } else {
+ binding.browserToolbar.showAsFixed(requireContext(), binding.engineView)
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+
+ requireContext().accessibilityManager.removeAccessibilityStateChangeListener(this)
+ binding.engineView.setActivityContext(null)
+
+ _binding = null
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ // This fragment might get destroyed before the user left immersive mode (e.g. by opening another URL from an
+ // app). In this case let's leave immersive mode now when the fragment gets destroyed.
+ requireActivity().exitImmersiveMode()
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ promptFeature.withFeature { it.onActivityResult(requestCode, data, resultCode) }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ private fun showCrashReporter(crash: Crash) {
+ val fragmentManager = requireActivity().supportFragmentManager
+
+ if (crashReporterIsVisible()) {
+ // We are already displaying the crash reporter
+ // No need to show another one.
+ return
+ }
+
+ val crashReporterFragment = CrashReporterFragment.create()
+
+ crashReporterFragment.onCloseTabPressed = { sendCrashReport ->
+ if (sendCrashReport) {
+ val crashReporter = requireComponents.crashReporter
+
+ GlobalScope.launch(Dispatchers.IO) { crashReporter.submitReport(crash) }
+ }
+
+ requireComponents.sessionUseCases.crashRecovery.invoke()
+ erase()
+ hideCrashReporter()
+ }
+
+ fragmentManager
+ .beginTransaction()
+ .addToBackStack(null)
+ .add(R.id.crash_container, crashReporterFragment, CrashReporterFragment.FRAGMENT_TAG)
+ .commit()
+
+ binding.crashContainer.isVisible = true
+ }
+
+ private fun hideCrashReporter() {
+ val fragmentManager = requireActivity().supportFragmentManager
+ val fragment = fragmentManager.findFragmentByTag(CrashReporterFragment.FRAGMENT_TAG)
+ ?: return
+
+ fragmentManager
+ .beginTransaction()
+ .remove(fragment)
+ .commit()
+
+ binding.crashContainer.isVisible = false
+ }
+
+ fun crashReporterIsVisible(): Boolean = requireActivity().supportFragmentManager.let {
+ it.findFragmentByTag(CrashReporterFragment.FRAGMENT_TAG)?.isVisible ?: false
+ }
+
+ private fun handleDownloadStopped(
+ state: DownloadState,
+ status: DownloadState.Status,
+ ) {
+ val extension =
+ MimeTypeMap.getFileExtensionFromUrl(URLEncoder.encode(state.filePath, "utf-8"))
+
+ when (status) {
+ DownloadState.Status.FAILED -> {
+ Downloads.downloadFailed.record(Downloads.DownloadFailedExtra(extension))
+ }
+
+ DownloadState.Status.PAUSED -> {
+ Downloads.downloadPaused.record(NoExtras())
+ }
+
+ DownloadState.Status.COMPLETED -> {
+ Downloads.downloadCompleted.record(NoExtras())
+
+ showDownloadCompletedSnackbar(state, extension)
+ }
+
+ else -> {
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ // Custom tab content should always be visible, even if the app is locked.
+ if (tab.isCustomTab()) {
+ view?.isVisible = true
+ }
+ }
+ private fun showDownloadCompletedSnackbar(
+ state: DownloadState,
+ extension: String?,
+ ) {
+ val snackbar = FocusSnackbar.make(
+ requireView(),
+ Snackbar.LENGTH_LONG,
+ )
+
+ snackbar.setText(
+ String.format(
+ requireContext().getString(R.string.download_snackbar_finished),
+ state.fileName,
+ ),
+ )
+
+ snackbar.setAction(getString(R.string.download_snackbar_open)) { context ->
+ val opened = AbstractFetchDownloadService.openFile(
+ applicationContext = context.applicationContext,
+ download = state,
+ )
+
+ if (!opened) {
+ Toast.makeText(
+ context,
+ getString(
+ mozilla.components.feature.downloads.R.string.mozac_feature_downloads_open_not_supported1,
+ extension,
+ ),
+ Toast.LENGTH_LONG,
+ ).show()
+ }
+
+ Downloads.openButtonTapped.record(
+ Downloads.OpenButtonTappedExtra(fileExtension = extension, openSuccessful = opened),
+ )
+ }
+
+ snackbar.show()
+ }
+
+ private fun showAddToHomescreenDialog() {
+ val fragmentManager = childFragmentManager
+
+ if (fragmentManager.findFragmentByTag(AddToHomescreenDialogFragment.FRAGMENT_TAG) != null) {
+ // We are already displaying a homescreen dialog fragment (Probably a restored fragment).
+ // No need to show another one.
+ return
+ }
+
+ val requestDesktop = tab.content.desktopMode
+
+ val addToHomescreenDialogFragment = AddToHomescreenDialogFragment.newInstance(
+ url = tab.content.url,
+ title = tab.content.titleOrDomain,
+ blockingEnabled = !tab.trackingProtection.ignoredOnTrackingProtection,
+ requestDesktop = requestDesktop,
+ )
+
+ try {
+ addToHomescreenDialogFragment.show(
+ fragmentManager,
+ AddToHomescreenDialogFragment.FRAGMENT_TAG,
+ )
+ } catch (e: IllegalStateException) {
+ // It can happen that at this point in time the activity is already in the background
+ // and onSaveInstanceState() has already been called. Fragment transactions are not
+ // allowed after that anymore. It's probably safe to guess that the user might not
+ // be interested in adding to homescreen now.
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ updateEngineColorScheme()
+
+ // Hide status bar background if the parent activity can be casted to MainActivity
+ (requireActivity() as? MainActivity)?.hideStatusBarBackground()
+ StatusBarUtils.getStatusBarHeight(binding.statusBarBackground) { statusBarHeight ->
+ binding.statusBarBackground.layoutParams.height = statusBarHeight
+ }
+
+ // Custom tab content should always be visible, even if the app is locked.
+ if (tab.isCustomTab()) {
+ view?.isVisible = true
+ }
+
+ context?.settings?.openLinksInExternalApp?.let { openLinksInExternalApp ->
+ val isCustomTab = tab.isCustomTab()
+ components?.appLinksInterceptor?.updateLaunchInApp {
+ openLinksInExternalApp || isCustomTab
+ }
+ }
+ }
+
+ private fun updateEngineColorScheme() {
+ val preferredColorScheme = requireComponents.settings.getPreferredColorScheme()
+ if (requireComponents.engine.settings.preferredColorScheme != preferredColorScheme) {
+ requireComponents.engine.settings.preferredColorScheme = preferredColorScheme
+ requireComponents.sessionUseCases.reload()
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ tabsPopup?.dismiss()
+ trackingProtectionPanel?.hide()
+ siteNotSupportedSnackBarScope?.cancel()
+ }
+
+ override fun onHomePressed() = pictureInPictureFeature?.onHomePressed() ?: false
+
+ @Suppress("ComplexMethod", "ReturnCount")
+ override fun onBackPressed(): Boolean {
+ if (findInPageIntegration.onBackPressed()) {
+ return true
+ } else if (fullScreenIntegration.onBackPressed()) {
+ return true
+ } else if (sessionFeature.get()?.onBackPressed() == true) {
+ return true
+ } else if (tab.source is SessionState.Source.Internal.TextSelection) {
+ erase()
+ return true
+ } else {
+ if (tab.source is SessionState.Source.External || tab.isCustomTab()) {
+ Browser.backButtonPressed.record(
+ Browser.BackButtonPressedExtra("erase_to_external_app"),
+ )
+
+ // This session has been started from a VIEW intent. Go back to the previous app
+ // immediately and erase the current browsing session.
+ erase()
+
+ // If there are no other sessions then we remove the whole task because otherwise
+ // the old session might still be partially visible in the app switcher.
+ if (requireComponents.store.state.privateTabs.isEmpty()) {
+ requireActivity().finishAndRemoveTask()
+ } else {
+ requireActivity().finish()
+ }
+ // We can't show a snackbar outside of the app. So let's show a toast instead.
+ Toast.makeText(context, R.string.feedback_erase_custom_tab, Toast.LENGTH_SHORT).show()
+ } else {
+ // Just go back to the home screen.
+ Browser.backButtonPressed.record(
+ Browser.BackButtonPressedExtra("erase_to_home"),
+ )
+
+ erase()
+ }
+ }
+
+ return true
+ }
+
+ fun erase(shouldEraseAllTabs: Boolean = false) {
+ if (shouldEraseAllTabs) {
+ requireComponents.appStore.dispatch(AppAction.NavigateUp(null))
+ requireComponents.tabsUseCases.removeAllTabs()
+ } else {
+ requireComponents.tabsUseCases.removeTab(tab.id)
+ requireComponents.appStore.dispatch(AppAction.NavigateUp(requireComponents.store.state.selectedTabId))
+ }
+ }
+
+ private fun shareCurrentUrl() {
+ val shareIntent = Intent(Intent.ACTION_SEND)
+ shareIntent.type = "text/plain"
+ shareIntent.putExtra(Intent.EXTRA_TEXT, tab.content.url)
+
+ val title = tab.content.title
+ if (title.isNotEmpty()) {
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, title)
+ }
+ startActivity(
+ IntentUtils.getIntentChooser(
+ context = requireContext(),
+ intent = shareIntent,
+ chooserTitle = getString(R.string.share_dialog_title),
+ ),
+ )
+ }
+
+ private fun openInBrowser() {
+ // Release the session from this view so that it can immediately be rendered by a different view
+ sessionFeature.get()?.release()
+
+ requireComponents.customTabsUseCases.migrate(tab.id)
+
+ val intent = Intent(context, MainActivity::class.java)
+ intent.action = Intent.ACTION_MAIN
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ startActivity(intent)
+
+ // Close this activity (and the task) since it is no longer displaying any session
+ val activity = activity
+ activity?.finishAndRemoveTask()
+ }
+
+ internal fun edit() {
+ requireComponents.appStore.dispatch(
+ AppAction.EditAction(tab.id),
+ )
+ }
+
+ private fun tabCounterListener() {
+ val openedTabs = requireComponents.store.state.tabs.size
+
+ tabsPopup = TabsPopup(binding.browserToolbar, requireComponents).also { currentTabsPopup ->
+ currentTabsPopup.showAsDropDown(
+ binding.browserToolbar,
+ 0,
+ 0,
+ Gravity.END,
+ )
+ }
+
+ TabCount.sessionButtonTapped.record(TabCount.SessionButtonTappedExtra(openedTabs))
+ }
+
+ private fun showFindInPageBar() {
+ findInPageIntegration.get()?.show(tab)
+ }
+
+ private fun openSelectBrowser() {
+ val browsers = Browsers.forUrl(requireContext(), tab.content.url)
+
+ val apps = browsers.installedBrowsers.filterNot { it.packageName == requireContext().packageName }
+ val store = if (browsers.hasFirefoxBrandedBrowserInstalled) {
+ null
+ } else {
+ InstallFirefoxActivity.resolveAppStore(requireContext())
+ }
+
+ val fragment = OpenWithFragment.newInstance(
+ apps.toTypedArray(),
+ tab.content.url,
+ store,
+ )
+ @Suppress("DEPRECATION")
+ fragment.show(requireFragmentManager(), OpenWithFragment.FRAGMENT_TAG)
+
+ OpenWith.listDisplayed.record(OpenWith.ListDisplayedExtra(apps.size))
+ }
+
+ internal fun closeCustomTab() {
+ requireComponents.customTabsUseCases.remove(tab.id)
+ requireActivity().finish()
+ }
+
+ private fun setShouldRequestDesktop(enabled: Boolean) {
+ if (enabled) {
+ PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
+ .putBoolean(
+ requireContext().getString(R.string.has_requested_desktop),
+ true,
+ ).apply()
+ }
+ requireComponents.sessionUseCases.requestDesktopSite(enabled, tab.id)
+ }
+
+ fun showTrackingProtectionPanel() {
+ trackingProtectionPanel = TrackingProtectionPanel(
+ context = requireContext(),
+ lifecycleOwner = this,
+ cookieBannerReducerStore = cookieBannerReducerStore,
+ tabUrl = tab.content.url,
+ isTrackingProtectionOn = tab.trackingProtection.ignoredOnTrackingProtection.not(),
+ isConnectionSecure = tab.content.securityInfo.secure,
+ blockedTrackersCount = requireContext().settings
+ .getTotalBlockedTrackersCount(),
+ toggleTrackingProtection = ::toggleTrackingProtection,
+ updateTrackingProtectionPolicy = { tracker, isEnabled ->
+ EngineSharedPreferencesListener(requireContext())
+ .updateTrackingProtectionPolicy(
+ source = EngineSharedPreferencesListener.ChangeSource.PANEL.source,
+ tracker = tracker,
+ isEnabled = isEnabled,
+ )
+ reloadCurrentTab()
+ },
+ showConnectionInfo = ::showConnectionInfo,
+ showCookieBannerExceptionsDetailsPanel = ::showCookieBannerExceptionDetailsPanel,
+ ).also { currentEtp -> context?.let { currentEtp.show() } }
+ }
+
+ private fun reloadCurrentTab() {
+ requireComponents.sessionUseCases.reload(tab.id)
+ }
+
+ private fun showCookieBannerExceptionDetailsPanel() {
+ val cookieBannerExceptionDetailsPanel = CookieBannerReducerDetailsPanel(
+ context = requireContext(),
+ cookieBannerReducerStore = cookieBannerReducerStore,
+ ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
+ tabUrl = tab.content.url,
+ goBack = { trackingProtectionPanel?.show() },
+ defaultCookieBannerInteractor = defaultCookieBannerInteractor,
+ )
+ trackingProtectionPanel?.hide()
+ cookieBannerExceptionDetailsPanel.show()
+ CookieBanner.visitedPanel.record(NoExtras())
+ }
+
+ private fun showConnectionInfo() {
+ val connectionInfoPanel = ConnectionDetailsPanel(
+ context = requireContext(),
+ tabTitle = tab.content.title,
+ tabUrl = tab.content.url,
+ isConnectionSecure = tab.content.securityInfo.secure,
+ goBack = { trackingProtectionPanel?.show() },
+ )
+ trackingProtectionPanel?.hide()
+ connectionInfoPanel.show()
+ }
+
+ private fun toggleTrackingProtection(enable: Boolean) {
+ with(requireComponents) {
+ if (enable) {
+ trackingProtectionUseCases.removeException(tab.id)
+ } else {
+ trackingProtectionUseCases.addException(tab.id, persistInPrivateMode = true)
+ }
+ }
+
+ reloadCurrentTab()
+
+ TrackingProtection.hasEverChangedEtp.set(true)
+ TrackingProtection.trackingProtectionChanged.record(
+ TrackingProtection.TrackingProtectionChangedExtra(
+ isEnabled = enable,
+ ),
+ )
+ }
+
+ private fun onUrlLongClicked(): Boolean {
+ val context = activity ?: return false
+
+ return if (tab.isCustomTab()) {
+ val clipBoard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val uri = tab.content.url.toUri()
+ clipBoard.setPrimaryClip(ClipData.newRawUri("Uri", uri))
+ Toast.makeText(context, getString(R.string.custom_tab_copy_url_action), Toast.LENGTH_SHORT).show()
+ true
+ } else {
+ false
+ }
+ }
+
+ private fun tryGetCustomTabId() = if (tab.isCustomTab()) {
+ tab.id
+ } else {
+ null
+ }
+
+ fun handleTabCrash(crash: Crash) {
+ showCrashReporter(crash)
+ }
+
+ companion object {
+ const val FRAGMENT_TAG = "browser"
+
+ private const val ARGUMENT_SESSION_UUID = "sessionUUID"
+
+ private const val REQUEST_KEY_DOWNLOAD_PERMISSIONS = "downloadFeature"
+ private const val REQUEST_KEY_PROMPT_PERMISSIONS = "promptFeature"
+ fun createForTab(tabId: String): BrowserFragment {
+ val fragment = BrowserFragment()
+ fragment.arguments = Bundle().apply {
+ putString(ARGUMENT_SESSION_UUID, tabId)
+ }
+ return fragment
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/CrashReporterFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/CrashReporterFragment.kt
new file mode 100644
index 0000000000..5969ab5fe3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/CrashReporterFragment.kt
@@ -0,0 +1,40 @@
+/* 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.focus.fragment
+
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.fragment.app.Fragment
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.CrashReporter
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentCrashReporterBinding
+
+class CrashReporterFragment : Fragment(R.layout.fragment_crash_reporter) {
+ var onCloseTabPressed: ((sendCrashReport: Boolean) -> Unit)? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val binding = FragmentCrashReporterBinding.bind(view)
+ binding.background.background =
+ AppCompatResources.getDrawable(requireContext(), R.drawable.ic_error_session_crashed)
+
+ CrashReporter.displayed.record(NoExtras())
+
+ binding.closeTabButton.setOnClickListener {
+ val wantsSubmitCrashReport = binding.sendCrashCheckbox.isChecked
+ CrashReporter.closeReport.record(CrashReporter.CloseReportExtra(wantsSubmitCrashReport))
+
+ onCloseTabPressed?.invoke(wantsSubmitCrashReport)
+ }
+ }
+
+ companion object {
+ val FRAGMENT_TAG = "crash-reporter"
+
+ fun create() = CrashReporterFragment()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/FirstrunFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/FirstrunFragment.kt
new file mode 100644
index 0000000000..25cdb1f74c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/FirstrunFragment.kt
@@ -0,0 +1,150 @@
+/* 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.focus.fragment
+
+import android.content.Context
+import android.os.Bundle
+import android.transition.TransitionInflater
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.viewpager.widget.ViewPager
+import mozilla.components.support.utils.StatusBarUtils
+import org.mozilla.focus.GleanMetrics.Onboarding
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentFirstrunBinding
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.firstrun.FirstrunPagerAdapter
+import org.mozilla.focus.state.AppAction
+import kotlin.math.abs
+
+class FirstrunFragment : Fragment(), View.OnClickListener {
+
+ private var _binding: FragmentFirstrunBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+
+ val transition =
+ TransitionInflater.from(context).inflateTransition(R.transition.firstrun_exit)
+
+ exitTransition = transition
+
+ // We will send a telemetry event whenever a new firstrun page is shown. However this page
+ // listener won't fire for the initial page we are showing. So we are going to firing here.
+ Onboarding.pageDisplayed.record(Onboarding.PageDisplayedExtra(0))
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentFirstrunBinding.inflate(inflater, container, false)
+
+ setupPager()
+
+ binding.tabs.setupWithViewPager(binding.pager, true)
+
+ binding.skip.setOnClickListener(this)
+
+ return binding.root
+ }
+
+ override fun onClick(view: View) {
+ val currentItem = binding.pager.currentItem
+ when (view.id) {
+ R.id.next -> {
+ binding.pager.currentItem = binding.pager.currentItem + 1
+ Onboarding.nextButtonTapped.record(Onboarding.NextButtonTappedExtra(currentItem))
+ }
+
+ R.id.skip -> {
+ finishFirstrun()
+ Onboarding.skipButtonTapped.record(Onboarding.SkipButtonTappedExtra(currentItem))
+ }
+
+ R.id.finish -> {
+ finishFirstrun()
+ Onboarding.finishButtonTapped.record(Onboarding.FinishButtonTappedExtra(currentItem))
+ }
+
+ else -> throw IllegalArgumentException("Unknown view")
+ }
+ }
+
+ @Suppress("MagicNumber")
+ private fun setupPager() {
+ val firstRunPagerAdapter = FirstrunPagerAdapter(requireContext(), this)
+ binding.pager.apply {
+ contentDescription = firstRunPagerAdapter.getPageAccessibilityDescription(0)
+ isFocusable = true
+
+ setPageTransformer(true) { page, position ->
+ page.alpha = 1 - 0.5f * abs(position)
+ }
+
+ clipToPadding = false
+ adapter = firstRunPagerAdapter
+ addOnPageChangeListener(
+ object : ViewPager.OnPageChangeListener {
+ override fun onPageSelected(position: Int) {
+ Onboarding.pageDisplayed.record(Onboarding.PageDisplayedExtra(0))
+
+ contentDescription =
+ firstRunPagerAdapter.getPageAccessibilityDescription(position)
+ }
+
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int,
+ ) {}
+
+ override fun onPageScrollStateChanged(state: Int) {}
+ },
+ )
+ }
+ }
+
+ private fun finishFirstrun() {
+ requireContext().settings.isFirstRun = false
+ val selectedTabId = requireComponents.store.state.selectedTabId
+ requireComponents.appStore.dispatch(AppAction.FinishFirstRun(selectedTabId))
+ }
+
+ override fun onResume() {
+ super.onResume()
+ StatusBarUtils.getStatusBarHeight(binding.background) { statusBarHeight ->
+ binding.background.setPadding(
+ 0,
+ statusBarHeight,
+ 0,
+ 0,
+ )
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ companion object {
+ const val FRAGMENT_TAG = "firstrun"
+
+ fun create(): FirstrunFragment {
+ val arguments = Bundle()
+
+ val fragment = FirstrunFragment()
+ fragment.arguments = arguments
+
+ return fragment
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/UrlInputFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/UrlInputFragment.kt
new file mode 100644
index 0000000000..9614ab02d7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/UrlInputFragment.kt
@@ -0,0 +1,632 @@
+/* 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.focus.fragment
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.view.isVisible
+import androidx.fragment.app.activityViewModels
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import mozilla.components.browser.domains.autocomplete.CustomDomainsProvider
+import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
+import mozilla.components.browser.state.selector.findTab
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.feature.top.sites.TopSitesConfig
+import mozilla.components.feature.top.sites.TopSitesFeature
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import mozilla.components.support.ktx.android.view.hideKeyboard
+import mozilla.components.support.ktx.util.URLStringUtils
+import mozilla.components.support.utils.StatusBarUtils
+import mozilla.components.support.utils.ThreadUtils
+import org.mozilla.focus.GleanMetrics.BrowserSearch
+import org.mozilla.focus.GleanMetrics.SearchBar
+import org.mozilla.focus.GleanMetrics.SearchWidget
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.databinding.FragmentUrlinputBinding
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.defaultSearchEngineName
+import org.mozilla.focus.ext.hasSearchTerms
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.input.InputToolbarIntegration
+import org.mozilla.focus.menu.home.HomeMenu
+import org.mozilla.focus.menu.home.HomeMenuItem
+import org.mozilla.focus.searchsuggestions.SearchSuggestionsViewModel
+import org.mozilla.focus.searchsuggestions.ui.SearchSuggestionsFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.topsites.DefaultTopSitesStorage.Companion.TOP_SITES_MAX_LIMIT
+import org.mozilla.focus.topsites.DefaultTopSitesView
+import org.mozilla.focus.topsites.TopSitesOverlay
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.utils.OneShotOnPreDrawListener
+import org.mozilla.focus.utils.SupportUtils
+import org.mozilla.focus.utils.ViewUtils
+import kotlin.coroutines.CoroutineContext
+
+class FocusCrashException : Exception()
+
+/**
+ * Fragment for displaying the URL input controls.
+ */
+// Refactoring the size and function count of this fragment is non-trivial at this point.
+// Therefore we ignore those violations for now.
+@Suppress("LargeClass", "TooManyFunctions")
+class UrlInputFragment :
+ BaseFragment(),
+ View.OnClickListener,
+ CoroutineScope {
+ companion object {
+ const val FRAGMENT_TAG = "url_input"
+
+ private const val ARGUMENT_ANIMATION = "animation"
+ private const val ARGUMENT_SESSION_UUID = "sesssion_uuid"
+
+ private const val ANIMATION_BROWSER_SCREEN = "browser_screen"
+
+ private const val ANIMATION_DURATION = 200
+
+ @JvmStatic
+ fun createWithoutSession(): UrlInputFragment {
+ val arguments = Bundle()
+
+ val fragment = UrlInputFragment()
+ fragment.arguments = arguments
+
+ return fragment
+ }
+
+ @JvmStatic
+ fun createWithTab(
+ tabId: String,
+ ): UrlInputFragment {
+ val arguments = Bundle()
+
+ arguments.putString(ARGUMENT_SESSION_UUID, tabId)
+ arguments.putString(ARGUMENT_ANIMATION, ANIMATION_BROWSER_SCREEN)
+
+ val fragment = UrlInputFragment()
+ fragment.arguments = arguments
+
+ return fragment
+ }
+ }
+
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+ private val shippedDomainsProvider = ShippedDomainsProvider()
+ private val customDomainsProvider = CustomDomainsProvider()
+ private var _binding: FragmentUrlinputBinding? = null
+ private val binding get() = _binding!!
+
+ private val searchSuggestionsViewModel: SearchSuggestionsViewModel by activityViewModels()
+
+ @Volatile
+ private var isAnimating: Boolean = false
+
+ var tab: TabSessionState? = null
+ private set
+
+ private val isOverlay: Boolean
+ get() = tab != null
+
+ private var isInitialized = false
+
+ private val toolbarIntegration = ViewBoundFeatureWrapper()
+ private val topSitesFeature = ViewBoundFeatureWrapper()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Get session from session manager if there's a session UUID in the fragment's arguments
+ arguments?.getString(ARGUMENT_SESSION_UUID)?.let { id ->
+ tab = requireComponents.store.state.findTab(id)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ if (job.isCancelled) {
+ job = Job()
+ }
+
+ activity?.let {
+ shippedDomainsProvider.initialize(it.applicationContext)
+ customDomainsProvider.initialize(it.applicationContext)
+ }
+
+ // Hide status bar background if the parent activity can be casted to MainActivity
+ (requireActivity() as? MainActivity)?.hideStatusBarBackground()
+ StatusBarUtils.getStatusBarHeight(binding.landingLayout) {
+ adjustViewToStatusBarHeight(it)
+ }
+
+ if (!isInitialized) {
+ // Explicitly switching to "edit mode" here in order to focus the toolbar and select
+ // all text in it. We only want to do this once per fragment.
+ binding.browserToolbar.editMode()
+ isInitialized = true
+ }
+
+ if (
+ requireComponents.settings.searchWidgetInstalled &&
+ requireComponents.appStore.state.showSearchWidgetSnackbar
+ ) {
+ ViewUtils.showBrandedSnackbar(view, R.string.promote_search_widget_snackbar_message, 0)
+ SearchWidget.widgetWasAdded.record(mozilla.telemetry.glean.private.NoExtras())
+ requireComponents.appStore.dispatch(AppAction.ShowSearchWidgetSnackBar(false))
+ }
+ }
+
+ override fun onPause() {
+ job.cancel()
+ super.onPause()
+ view?.hideKeyboard()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun adjustViewToStatusBarHeight(statusBarHeight: Int) {
+ val inputHeight = resources.getDimension(R.dimen.urlinput_height)
+
+ binding.urlInputLayout.layoutParams.height = (inputHeight + statusBarHeight).toInt()
+
+ val topMargin = (inputHeight + statusBarHeight).toInt()
+
+ if (binding.searchViewContainer.layoutParams is ViewGroup.MarginLayoutParams) {
+ val marginParams =
+ binding.searchViewContainer.layoutParams as ViewGroup.MarginLayoutParams
+ marginParams.topMargin = topMargin
+ }
+
+ if (binding.landingLayout.layoutParams is ViewGroup.MarginLayoutParams) {
+ val marginParams = binding.landingLayout.layoutParams as ViewGroup.MarginLayoutParams
+ marginParams.topMargin = topMargin
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentUrlinputBinding.inflate(inflater, container, false)
+
+ binding.topSites.setContent {
+ FocusTheme {
+ TopSitesOverlay()
+ }
+ }
+ return binding.root
+ }
+
+ @Suppress("LongMethod")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ childFragmentManager.beginTransaction()
+ .replace(binding.searchViewContainer.id, SearchSuggestionsFragment.create())
+ .commit()
+
+ searchSuggestionsViewModel.selectedSearchSuggestion.observe(
+ viewLifecycleOwner,
+ ) {
+ val isSuggestion = searchSuggestionsViewModel.searchQuery.value != it
+ it?.let {
+ if (searchSuggestionsViewModel.alwaysSearch) {
+ onSearch(it, isSuggestion = false, alwaysSearch = true)
+ } else {
+ onSearch(it, isSuggestion)
+ }
+ searchSuggestionsViewModel.clearSearchSuggestion()
+ }
+ }
+
+ searchSuggestionsViewModel.autocompleteSuggestion.observe(viewLifecycleOwner) { text ->
+ if (text != null) {
+ searchSuggestionsViewModel.clearAutocompleteSuggestion()
+ binding.browserToolbar.setSearchTerms(text)
+ }
+ }
+
+ binding.browserToolbar.private = true
+
+ toolbarIntegration.set(
+ InputToolbarIntegration(
+ binding.browserToolbar,
+ fragment = this,
+ shippedDomainsProvider = shippedDomainsProvider,
+ customDomainsProvider = customDomainsProvider,
+ ),
+ owner = this,
+ view = binding.browserToolbar,
+ )
+
+ topSitesFeature.set(
+ feature = TopSitesFeature(
+ view = DefaultTopSitesView(requireComponents.appStore),
+ storage = requireComponents.topSitesStorage,
+ config = {
+ TopSitesConfig(
+ totalSites = TOP_SITES_MAX_LIMIT,
+ frecencyConfig = null,
+ providerConfig = null,
+ )
+ },
+ ),
+ owner = this,
+ view = view,
+ )
+
+ binding.dismissView.setOnClickListener(this)
+
+ OneShotOnPreDrawListener(binding.urlInputContainerView) {
+ animateFirstDraw()
+ true
+ }
+
+ if (isOverlay) {
+ binding.landingLayout.isVisible = false
+ } else {
+ binding.backgroundView.background = AppCompatResources.getDrawable(
+ requireContext(),
+ R.drawable.home_background,
+ )
+
+ binding.dismissView.isVisible = false
+
+ binding.menuView.isVisible = true
+ }
+
+ tab?.let { tab ->
+ binding.browserToolbar.url = if (tab.content.hasSearchTerms) {
+ tab.content.searchTerms
+ } else {
+ tab.content.url
+ }
+
+ binding.searchViewContainer.isVisible = false
+ binding.menuView.isVisible = false
+ }
+
+ binding.browserToolbar.editMode()
+ setHomeMenu()
+ }
+
+ private fun setHomeMenu() {
+ binding.menuView.menuBuilder = HomeMenu(requireContext()) { menuItem ->
+ when (menuItem) {
+ is HomeMenuItem.Help -> openHelpPage()
+ is HomeMenuItem.Settings -> openSettingsPage()
+ }
+ }.getMenuBuilder()
+ }
+
+ private fun openHelpPage() {
+ requireComponents.tabsUseCases.addTab(
+ SupportUtils.HELP_URL,
+ source = SessionState.Source.Internal.Menu,
+ private = true,
+ )
+ }
+
+ private fun openSettingsPage() {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.Start),
+ )
+ }
+
+ fun onBackPressed(): Boolean {
+ if (isOverlay) {
+ animateAndDismiss()
+ return true
+ }
+
+ return false
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ activity?.let {
+ if (requireContext().settings.isFirstRun) return@onStart
+ }
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+
+ if (newConfig.orientation != Configuration.ORIENTATION_UNDEFINED) {
+ // Make sure we update the background for landscape / portrait orientations.
+ binding.backgroundView.background = AppCompatResources.getDrawable(
+ requireContext(),
+ R.drawable.home_background,
+ )
+ }
+ }
+
+ // This method triggers the complexity warning. However it's actually not that hard to understand.
+ @Suppress("ComplexMethod")
+ override fun onClick(view: View) {
+ if (view.id == R.id.dismissView) {
+ handleDismiss()
+ } else {
+ throw IllegalStateException("Unhandled view in onClick()")
+ }
+ }
+
+ private fun handleDismiss() {
+ if (isOverlay) {
+ animateAndDismiss()
+ } else {
+ // This is a bit hacky, but emulates what the legacy toolbar did before we replaced
+ // it with `browser-toolbar`. First we clear the text and then we invoke the "text
+ // changed" callback manually for this change.
+ binding.browserToolbar.edit.updateUrl("", false)
+ onTextChange("")
+ }
+ }
+
+ private fun animateFirstDraw() {
+ if (ANIMATION_BROWSER_SCREEN == arguments?.getString(ARGUMENT_ANIMATION)) {
+ playVisibilityAnimation(false)
+ }
+ }
+
+ private fun animateAndDismiss() {
+ ThreadUtils.assertOnUiThread()
+
+ if (isAnimating) {
+ // We are already animating some state change. Ignore all other requests.
+ return
+ }
+
+ // Don't allow any more clicks: dismissView is still visible until the animation ends,
+ // but we don't want to restart animations and/or trigger hiding again (which could potentially
+ // cause crashes since we don't know what state we're in). Ignoring further clicks is the simplest
+ // solution, since dismissView is about to disappear anyway.
+ binding.dismissView.isClickable = false
+
+ if (ANIMATION_BROWSER_SCREEN == arguments?.getString(ARGUMENT_ANIMATION)) {
+ playVisibilityAnimation(true)
+ } else {
+ dismiss()
+ }
+ }
+
+ /**
+ * This animation is quite complex. The 'reverse' flag controls whether we want to show the UI
+ * (false) or whether we are going to hide it (true). Additionally the animation is slightly
+ * different depending on whether this fragment is shown as an overlay on top of other fragments
+ * or if it draws its own background.
+ */
+ // This method correctly triggers a complexity warning. This method is indeed very and too complex.
+ // However refactoring it is not trivial at this point so we ignore the warning for now.
+ @Suppress("ComplexMethod")
+ private fun playVisibilityAnimation(reverse: Boolean) {
+ if (isAnimating) {
+ // We are already animating, let's ignore another request.
+ return
+ }
+
+ isAnimating = true
+
+ val xyOffset = (
+ if (isOverlay) {
+ (binding.urlInputContainerView.layoutParams as FrameLayout.LayoutParams).bottomMargin
+ } else {
+ 0
+ }
+ ).toFloat()
+
+ val width = binding.urlInputBackgroundView.width.toFloat()
+ val height = binding.urlInputBackgroundView.height.toFloat()
+
+ val widthScale = if (isOverlay) {
+ (width + 2 * xyOffset) / width
+ } else {
+ 1f
+ }
+
+ val heightScale = if (isOverlay) {
+ (height + 2 * xyOffset) / height
+ } else {
+ 1f
+ }
+
+ if (!reverse) {
+ binding.urlInputBackgroundView.apply {
+ pivotX = 0f
+ pivotY = 0f
+ scaleX = widthScale
+ scaleY = heightScale
+ translationX = -xyOffset
+ translationY = -xyOffset
+ }
+ }
+
+ // Let the URL input use the full width/height and then shrink to the actual size
+ binding.urlInputBackgroundView.animate()
+ .setDuration(ANIMATION_DURATION.toLong())
+ .scaleX(if (reverse) widthScale else 1f)
+ .scaleY(if (reverse) heightScale else 1f)
+ .alpha((if (reverse && isOverlay) 0 else 1).toFloat())
+ .translationX(if (reverse) -xyOffset else 0f)
+ .translationY(if (reverse) -xyOffset else 0f)
+ .setListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ if (reverse) {
+ if (isOverlay) {
+ dismiss()
+ }
+ }
+
+ isAnimating = false
+ }
+ },
+ )
+
+ if (reverse) {
+ binding.toolbarBottomBorder.isVisible = true
+
+ if (!isOverlay) {
+ binding.dismissView.isVisible = false
+ binding.menuView.isVisible = true
+ }
+ } else {
+ binding.toolbarBottomBorder.isVisible = false
+ }
+ }
+
+ private fun dismiss() {
+ // This method is called from animation callbacks. In the short time frame between the animation
+ // starting and ending the activity can be paused. In this case this code can throw an
+ // IllegalStateException because we already saved the state (of the activity / fragment) before
+ // this transaction is committed. To avoid this we commit while allowing a state loss here.
+ // We do not save any state in this fragment (It's getting destroyed) so this should not be a problem.
+
+ context?.components?.appStore?.dispatch(AppAction.FinishEdit(tab!!.id))
+ }
+
+ internal fun onCommit(input: String) {
+ if (input.trim { it <= ' ' }.isNotEmpty()) {
+ handleCrashTrigger(input)
+
+ ViewUtils.hideKeyboard(binding.browserToolbar)
+
+ val isUrl = URLStringUtils.isURLLike(input)
+ if (isUrl) {
+ openUrl(URLStringUtils.toNormalizedURL(input))
+ } else {
+ search(input)
+ }
+
+ if (isUrl) {
+ SearchBar.enteredUrl.record(NoExtras())
+ } else {
+ val defaultSearchEngineName =
+ requireComponents.store.defaultSearchEngineName().lowercase()
+ SearchBar.performedSearch.record(
+ SearchBar.PerformedSearchExtra(defaultSearchEngineName),
+ )
+ BrowserSearch.searchCount["$defaultSearchEngineName.action"].add()
+ }
+ }
+ }
+
+ private fun handleCrashTrigger(input: String) {
+ if (input == "focus:crash") {
+ throw FocusCrashException()
+ }
+ }
+
+ private fun onSearch(
+ query: String,
+ @Suppress("UNUSED_PARAMETER") isSuggestion: Boolean = false,
+ alwaysSearch: Boolean = false,
+ ) {
+ if (alwaysSearch) {
+ search(query)
+ } else {
+ if (URLStringUtils.isURLLike(query)) {
+ openUrl(URLStringUtils.toNormalizedURL(query))
+ } else {
+ search(query)
+ }
+ }
+
+ val defaultSearchEngineName = requireComponents.store.defaultSearchEngineName().lowercase()
+ BrowserSearch.searchCount["$defaultSearchEngineName.suggestion"].add()
+ }
+
+ private fun search(searchTerms: String) {
+ val tab = tab
+ if (tab != null) {
+ requireComponents.searchUseCases.defaultSearch.invoke(searchTerms, tab.id)
+ requireComponents.appStore.dispatch(AppAction.FinishEdit(tab.id))
+ } else {
+ requireComponents.searchUseCases.newPrivateTabSearch.invoke(
+ searchTerms = searchTerms,
+ source = SessionState.Source.Internal.UserEntered,
+ )
+ }
+
+ searchSuggestionsViewModel.setSearchQuery("")
+ }
+
+ private fun openUrl(url: String) {
+ when (url) {
+ "focus:about" -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.About),
+ )
+ return
+ }
+ }
+
+ val tab = tab
+ if (tab != null) {
+ requireComponents.sessionUseCases.loadUrl(url, tab.id)
+
+ requireComponents.appStore.dispatch(AppAction.FinishEdit(tab.id))
+ } else {
+ requireComponents.tabsUseCases.addTab(
+ url,
+ source = SessionState.Source.Internal.UserEntered,
+ selectTab = true,
+ private = true,
+ )
+ }
+
+ searchSuggestionsViewModel.setSearchQuery("")
+ }
+
+ internal fun onStartEditing() {
+ if (tab != null) {
+ binding.searchViewContainer.isVisible = true
+ }
+ }
+
+ internal fun onCancelEditing() {
+ handleDismiss()
+ }
+
+ internal fun onTextChange(text: String) {
+ searchSuggestionsViewModel.setSearchQuery(text)
+
+ if (text.trim { it <= ' ' }.isEmpty()) {
+ binding.searchViewContainer.isVisible = false
+
+ if (!isOverlay) {
+ playVisibilityAnimation(true)
+ }
+ } else {
+ binding.menuView.isVisible = false
+
+ if (!isOverlay && binding.dismissView.isVisible != true) {
+ playVisibilityAnimation(false)
+ binding.dismissView.isVisible = true
+ }
+
+ binding.searchViewContainer.isVisible = true
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/AboutFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/AboutFragment.kt
new file mode 100644
index 0000000000..cf9a80bd06
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/AboutFragment.kt
@@ -0,0 +1,209 @@
+/* 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.focus.fragment.about
+
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.Start
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextDirection
+import androidx.compose.ui.unit.dp
+import androidx.core.content.pm.PackageInfoCompat
+import kotlinx.coroutines.Job
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.support.utils.ext.getPackageInfoCompat
+import org.mozilla.focus.BuildConfig
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentAboutBinding
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsLikeFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+import org.mozilla.focus.ui.theme.focusTypography
+import org.mozilla.focus.utils.SupportUtils.manifestoURL
+import org.mozilla.geckoview.BuildConfig as GeckoViewBuildConfig
+
+class AboutFragment : BaseSettingsLikeFragment() {
+
+ private lateinit var secretSettingsUnlocker: SecretSettingsUnlocker
+
+ private val openLearnMore = {
+ val tabId = requireContext().components.tabsUseCases.addTab(
+ url = manifestoURL,
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ requireContext().components.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+
+ override fun onResume() {
+ super.onResume()
+ showToolbar(getString(R.string.menu_about))
+ secretSettingsUnlocker.resetCounter()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ val binding = FragmentAboutBinding.inflate(inflater, container, false)
+
+ val appName = requireContext().resources.getString(R.string.app_name)
+ val aboutContent =
+ requireContext().getString(R.string.about_content, appName, "")
+
+ val learnMore = aboutContent
+ .substringAfter("")
+ .substringBefore("
")
+
+ val content =
+ aboutContent
+ .replace("", "\u2022 \u0009 ")
+ .replace(" ", "\n")
+ .replace("", "")
+ .replace("", "\n")
+ .replace("
", "")
+ .replaceAfter(" ", "")
+ .replace(" ", "")
+
+ secretSettingsUnlocker = SecretSettingsUnlocker(requireContext())
+
+ binding.aboutPageContent.setContent {
+ AboutPageContent(
+ getAboutHeader(),
+ content,
+ learnMore,
+ secretSettingsUnlocker,
+ openLearnMore,
+ )
+ }
+ return binding.root
+ }
+
+ private fun getAboutHeader(): String {
+ val gecko = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) " \uD83E\uDD8E " else " GV: "
+ val engineIndicator = gecko + GeckoViewBuildConfig.MOZ_APP_VERSION + "-" + GeckoViewBuildConfig.MOZ_APP_BUILDID
+ val servicesAbbreviation = getString(R.string.services_abbreviation)
+ val servicesIndicator = mozilla.components.Build.applicationServicesVersion
+ val packageInfo = requireContext().packageManager.getPackageInfoCompat(requireContext().packageName, 0)
+ val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
+ val vcsHash = if (BuildConfig.VCS_HASH.isNotBlank()) ", ${BuildConfig.VCS_HASH}" else ""
+
+ @Suppress("ImplicitDefaultLocale") // We want LTR in all cases as the version is not translatable.
+ return String.format(
+ "%s (Build #%s)%s\n%s: %s",
+ packageInfo.versionName,
+ versionCode + engineIndicator,
+ vcsHash,
+ servicesAbbreviation,
+ servicesIndicator,
+ )
+ }
+}
+
+@Composable
+private fun AboutPageContent(
+ aboutVersion: String,
+ content: String,
+ learnMore: String,
+ secretSettingsUnlocker: SecretSettingsUnlocker,
+ openLearnMore: () -> Job,
+) {
+ FocusTheme {
+ Column(
+ modifier = Modifier
+ .padding(8.dp)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+
+ ) {
+ LogoIcon(secretSettingsUnlocker)
+ VersionInfo(aboutVersion)
+ AboutContent(content)
+ LearnMoreLink(learnMore, openLearnMore)
+ }
+ }
+}
+
+@Composable
+private fun LogoIcon(secretSettingsUnlocker: SecretSettingsUnlocker) {
+ Image(
+ painter = painterResource(R.drawable.wordmark2),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(4.dp)
+ .clickable {
+ secretSettingsUnlocker.increment()
+ },
+ )
+}
+
+@Composable
+private fun VersionInfo(aboutVersion: String) {
+ Text(
+ text = aboutVersion,
+ color = focusColors.aboutPageText,
+ style = focusTypography.body1.copy(
+ // Use LTR in all cases since the version is not translatable.
+ textDirection = TextDirection.Ltr,
+ ),
+ modifier = Modifier
+ .padding(10.dp),
+ )
+}
+
+@Composable
+private fun AboutContent(content: String) {
+ Text(
+ text = content,
+ color = focusColors.aboutPageText,
+ style = focusTypography.body1,
+ modifier = Modifier
+ .padding(10.dp),
+ )
+}
+
+@Composable
+fun ColumnScope.LearnMoreLink(
+ learnMore: String,
+ openLearnMore: () -> Job,
+) {
+ Text(
+ text = learnMore,
+ color = focusColors.aboutPageLink,
+ style = focusTypography.links,
+ modifier = Modifier
+ .padding(10.dp)
+ .fillMaxWidth()
+ .align(Start)
+ .clickable {
+ openLearnMore()
+ },
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/SecretSettingsUnlocker.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/SecretSettingsUnlocker.kt
new file mode 100644
index 0000000000..4f02d1abf2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/SecretSettingsUnlocker.kt
@@ -0,0 +1,59 @@
+/* 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.focus.fragment.about
+
+import android.content.Context
+import android.widget.Toast
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.state.AppAction
+
+/**
+ * Triggers the "secret" debug menu when logoView is tapped 5 times.
+ */
+class SecretSettingsUnlocker(private val context: Context) {
+
+ private var secretSettingsClicks = 0
+ private var lastDebugMenuToast: Toast? = null
+
+ /**
+ * Reset the [secretSettingsClicks] counter.
+ */
+ fun resetCounter() {
+ secretSettingsClicks = 0
+ }
+
+ fun increment() {
+ // Because the user will mostly likely tap the logo in rapid succession,
+ // we ensure only 1 toast is shown at any given time.
+ lastDebugMenuToast?.cancel()
+ secretSettingsClicks += 1
+ when (secretSettingsClicks) {
+ in 2 until SECRET_DEBUG_MENU_CLICKS -> {
+ val clicksLeft = SECRET_DEBUG_MENU_CLICKS - secretSettingsClicks
+ val toast = Toast.makeText(
+ context,
+ context.getString(R.string.about_debug_menu_toast_progress, clicksLeft),
+ Toast.LENGTH_SHORT,
+ )
+ toast.show()
+ lastDebugMenuToast = toast
+ }
+ SECRET_DEBUG_MENU_CLICKS -> {
+ Toast.makeText(
+ context,
+ R.string.about_debug_menu_toast_done,
+ Toast.LENGTH_LONG,
+ ).show()
+ context.components.appStore.dispatch(AppAction.SecretSettingsStateChange(true))
+ }
+ }
+ }
+
+ companion object {
+ // Number of clicks on the app logo to enable the "secret" debug menu.
+ private const val SECRET_DEBUG_MENU_CLICKS = 5
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingController.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingController.kt
new file mode 100644
index 0000000000..434bcac643
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingController.kt
@@ -0,0 +1,100 @@
+/* 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.focus.fragment.onboarding
+
+import android.app.Activity
+import android.app.role.RoleManager
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.utils.Browsers
+import mozilla.components.support.utils.ext.navigateToDefaultBrowserAppsSettings
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+
+interface OnboardingController {
+ fun handleFinishOnBoarding()
+ fun handleGetStartedButtonClicked()
+ fun handleMakeFocusDefaultBrowserButtonClicked(activityResultLauncher: ActivityResultLauncher)
+ fun handleActivityResultImplementation(activityResult: ActivityResult)
+}
+
+class DefaultOnboardingController(
+ private val onboardingStorage: OnboardingStorage,
+ val appStore: AppStore,
+ val context: Context,
+ val selectedTabId: String?,
+) : OnboardingController {
+
+ override fun handleFinishOnBoarding() {
+ context.settings.isFirstRun = false
+ appStore.dispatch(AppAction.FinishFirstRun(selectedTabId))
+ }
+
+ override fun handleGetStartedButtonClicked() {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M || Browsers.all(context).isDefaultBrowser) {
+ handleFinishOnBoarding()
+ } else {
+ navigateToOnBoardingSecondScreen()
+ }
+ }
+
+ override fun handleMakeFocusDefaultBrowserButtonClicked(activityResultLauncher: ActivityResultLauncher) {
+ val isDefault = Browsers.all(context).isDefaultBrowser
+ if (isDefault) {
+ handleFinishOnBoarding()
+ } else {
+ makeFocusDefaultBrowser(activityResultLauncher)
+ }
+ }
+
+ override fun handleActivityResultImplementation(activityResult: ActivityResult) {
+ if (activityResult.resultCode == Activity.RESULT_OK && Browsers.all(context).isDefaultBrowser) {
+ handleFinishOnBoarding()
+ }
+ }
+
+ private fun navigateToOnBoardingSecondScreen() {
+ onboardingStorage.saveCurrentOnboardingStepInSharePref(OnboardingStep.ON_BOARDING_SECOND_SCREEN)
+ appStore.dispatch(AppAction.ShowOnboardingSecondScreen)
+ }
+
+ private fun makeFocusDefaultBrowser(activityResultLauncher: ActivityResultLauncher) {
+ when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
+ context.getSystemService(RoleManager::class.java).also {
+ if (
+ it.isRoleAvailable(RoleManager.ROLE_BROWSER) &&
+ !it.isRoleHeld(RoleManager.ROLE_BROWSER)
+ ) {
+ try {
+ activityResultLauncher.launch(it.createRequestRoleIntent(RoleManager.ROLE_BROWSER))
+ } catch (e: ActivityNotFoundException) {
+ Logger(TAG).error(
+ "ActivityNotFoundException " +
+ e.message.toString(),
+ )
+ handleFinishOnBoarding()
+ }
+ } else {
+ context.navigateToDefaultBrowserAppsSettings()
+ }
+ }
+ }
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
+ context.navigateToDefaultBrowserAppsSettings()
+ }
+ }
+ }
+
+ companion object {
+ const val TAG = "OnboardingController"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstFragment.kt
new file mode 100644
index 0000000000..4e38b38693
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstFragment.kt
@@ -0,0 +1,62 @@
+/* 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.focus.fragment.onboarding
+
+import android.content.Context
+import android.os.Bundle
+import android.transition.TransitionInflater
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.Onboarding
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ui.theme.FocusTheme
+
+class OnboardingFirstFragment : Fragment() {
+ private lateinit var onboardingInteractor: OnboardingInteractor
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ val transition =
+ TransitionInflater.from(context).inflateTransition(R.transition.firstrun_exit)
+ exitTransition = transition
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ onboardingInteractor = DefaultOnboardingInteractor(
+ DefaultOnboardingController(
+ onboardingStorage = OnboardingStorage(requireContext()),
+ appStore = requireComponents.appStore,
+ context = requireActivity(),
+ selectedTabId = requireComponents.store.state.selectedTabId,
+ ),
+ )
+ return ComposeView(requireContext()).apply {
+ setContent {
+ FocusTheme {
+ OnBoardingFirstScreenCompose(
+ onGetStartedButtonClicked = {
+ Onboarding.getStartedButton.record(NoExtras())
+ onboardingInteractor.onGetStartedButtonClicked()
+ },
+ onCloseButtonClick = {
+ Onboarding.firstScreenCloseButton.record(NoExtras())
+ onboardingInteractor.onFinishOnBoarding()
+ },
+ )
+ }
+ }
+ isTransitionGroup = true
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt
new file mode 100644
index 0000000000..fb705e1ed0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt
@@ -0,0 +1,165 @@
+/* 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.focus.fragment.onboarding
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+import org.mozilla.focus.ui.theme.focusTypography
+
+@Composable
+@Preview
+private fun OnBoardingFirstScreenComposePreview() {
+ FocusTheme {
+ OnBoardingFirstScreenCompose({}, {})
+ }
+}
+
+/**
+ * Displays the first onBoarding screen
+ *
+ * @param onGetStartedButtonClicked Will be called when the user clicks on get started button.
+ * @param onCloseButtonClick The lambda to be invoked when close button icon is pressed.
+ */
+@Composable
+fun OnBoardingFirstScreenCompose(
+ onGetStartedButtonClicked: () -> Unit,
+ onCloseButtonClick: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ brush = Brush.linearGradient(
+ colors = listOf(
+ colorResource(R.color.home_screen_modal_gradient_one),
+ colorResource(R.color.home_screen_modal_gradient_two),
+ colorResource(R.color.home_screen_modal_gradient_three),
+ colorResource(R.color.home_screen_modal_gradient_four),
+ colorResource(R.color.home_screen_modal_gradient_five),
+ colorResource(R.color.home_screen_modal_gradient_six),
+ ),
+ end = Offset(0f, Float.POSITIVE_INFINITY),
+ start = Offset(Float.POSITIVE_INFINITY, 0f),
+ ),
+ ),
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 60.dp, end = 20.dp),
+ horizontalAlignment = Alignment.End,
+ ) {
+ CloseButton(onCloseButtonClick)
+ }
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ painter = painterResource(R.drawable.onboarding_logo),
+ contentDescription = LocalContext.current.getString(R.string.app_name),
+ modifier = Modifier
+ .size(150.dp, 150.dp),
+ )
+ Text(
+ text = stringResource(
+ R.string.onboarding_first_screen_title,
+ stringResource(R.string.app_name),
+ ),
+ modifier = Modifier
+ .padding(top = 32.dp, start = 16.dp, end = 16.dp),
+ textAlign = TextAlign.Center,
+ style = focusTypography.onboardingTitle,
+ )
+ Text(
+ text = stringResource(
+ R.string.onboarding_first_screen_subtitle,
+ ),
+ modifier = Modifier
+ .padding(top = 16.dp, start = 16.dp, end = 16.dp),
+ textAlign = TextAlign.Center,
+ style = focusTypography.onboardingSubtitle,
+ )
+ ComponentGoToOnBoardingSecondScreen {
+ onGetStartedButtonClicked()
+ }
+ }
+ }
+}
+
+@Composable
+private fun CloseButton(onCloseButtonClick: () -> Unit) {
+ IconButton(
+ modifier = Modifier
+ .size(48.dp)
+ .background(
+ colorResource(R.color.onboardingCloseButtonColor),
+ shape = CircleShape,
+ ),
+ onClick = onCloseButtonClick,
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.mozac_ic_cross_24),
+ contentDescription = stringResource(R.string.onboarding_close_button_content_description),
+ tint = focusColors.closeIcon,
+ )
+ }
+}
+
+@Composable
+private fun ComponentGoToOnBoardingSecondScreen(goToOnBoardingSecondScreen: () -> Unit) {
+ Button(
+ onClick = goToOnBoardingSecondScreen,
+ modifier = Modifier
+ .padding(top = 40.dp, start = 16.dp, end = 16.dp)
+ .fillMaxWidth(),
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = colorResource(R.color.onboardingButtonOneColor),
+ ),
+ ) {
+ Text(
+ text = AnnotatedString(
+ LocalContext.current.resources.getString(
+ R.string.onboarding_first_screen_button_text,
+ ),
+ ),
+ color = PhotonColors.White,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingInteractor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingInteractor.kt
new file mode 100644
index 0000000000..b60c4c2fa3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingInteractor.kt
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.focus.fragment.onboarding
+
+import android.content.Intent
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+
+interface OnboardingInteractor {
+ fun onFinishOnBoarding()
+ fun onGetStartedButtonClicked()
+ fun onMakeFocusDefaultBrowserButtonClicked(activityResultLauncher: ActivityResultLauncher)
+ fun onActivityResultImplementation(activityResult: ActivityResult)
+}
+
+class DefaultOnboardingInteractor(private val controller: OnboardingController) : OnboardingInteractor {
+ override fun onFinishOnBoarding() {
+ controller.handleFinishOnBoarding()
+ }
+
+ override fun onGetStartedButtonClicked() {
+ controller.handleGetStartedButtonClicked()
+ }
+
+ override fun onMakeFocusDefaultBrowserButtonClicked(activityResultLauncher: ActivityResultLauncher) {
+ controller.handleMakeFocusDefaultBrowserButtonClicked(activityResultLauncher)
+ }
+
+ override fun onActivityResultImplementation(activityResult: ActivityResult) {
+ controller.handleActivityResultImplementation(activityResult)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondFragment.kt
new file mode 100644
index 0000000000..72bfa8ffb2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondFragment.kt
@@ -0,0 +1,88 @@
+/* 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.focus.fragment.onboarding
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.transition.TransitionInflater
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import mozilla.components.support.utils.Browsers
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.Onboarding
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ui.theme.FocusTheme
+
+class OnboardingSecondFragment : Fragment() {
+ private lateinit var onboardingInteractor: OnboardingInteractor
+
+ private var activityResultLauncher: ActivityResultLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult(),
+ ) {
+ onboardingInteractor.onActivityResultImplementation(it)
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ val transition =
+ TransitionInflater.from(context).inflateTransition(R.transition.firstrun_exit)
+ exitTransition = transition
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ onboardingInteractor = DefaultOnboardingInteractor(
+ DefaultOnboardingController(
+ onboardingStorage = OnboardingStorage(requireContext()),
+ appStore = requireComponents.appStore,
+ context = requireActivity(),
+ selectedTabId = requireComponents.store.state.selectedTabId,
+ ),
+ )
+ return ComposeView(requireContext()).apply {
+ setContent {
+ FocusTheme {
+ OnBoardingSecondScreenCompose(
+ setAsDefaultBrowser = {
+ Onboarding.defaultBrowserButton.record(NoExtras())
+ onboardingInteractor.onMakeFocusDefaultBrowserButtonClicked(activityResultLauncher)
+ },
+ skipScreen = {
+ Onboarding.skipButton.record(NoExtras())
+ onboardingInteractor.onFinishOnBoarding()
+ },
+ onCloseButtonClick = {
+ Onboarding.secondScreenCloseButton.record(NoExtras())
+ onboardingInteractor.onFinishOnBoarding()
+ },
+ )
+ }
+ }
+ isTransitionGroup = true
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // check if the default browser was changed from OS settings for devices with Android 7,8 and 9.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
+ Browsers.all(requireContext()).isDefaultBrowser
+ ) {
+ onboardingInteractor.onFinishOnBoarding()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt
new file mode 100644
index 0000000000..67323f34b0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt
@@ -0,0 +1,194 @@
+/* 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.focus.fragment.onboarding
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+import org.mozilla.focus.ui.theme.focusTypography
+
+@Composable
+@Preview
+private fun OnBoardingSecondScreenComposePreview() {
+ FocusTheme {
+ OnBoardingSecondScreenCompose({}, {}, {})
+ }
+}
+
+/**
+ * Displays the second onBoarding screen
+ *
+ * @param setAsDefaultBrowser Will be called when the user clicks on SetDefaultBrowser button.
+ * @param skipScreen Will be called when the user clicks on Skip button.
+ * @param onCloseButtonClick The lambda to be invoked when close button icon is pressed.
+ */
+@Composable
+@Suppress("LongMethod")
+fun OnBoardingSecondScreenCompose(
+ setAsDefaultBrowser: () -> Unit,
+ skipScreen: () -> Unit,
+ onCloseButtonClick: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ brush = Brush.linearGradient(
+ colors = listOf(
+ colorResource(R.color.home_screen_modal_gradient_one),
+ colorResource(R.color.home_screen_modal_gradient_two),
+ colorResource(R.color.home_screen_modal_gradient_three),
+ colorResource(R.color.home_screen_modal_gradient_four),
+ colorResource(R.color.home_screen_modal_gradient_five),
+ colorResource(R.color.home_screen_modal_gradient_six),
+ ),
+ end = Offset(0f, Float.POSITIVE_INFINITY),
+ start = Offset(Float.POSITIVE_INFINITY, 0f),
+ ),
+ ),
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 60.dp, end = 20.dp),
+ horizontalAlignment = Alignment.End,
+ ) {
+ CloseButton(onCloseButtonClick)
+ }
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ painter = painterResource(R.drawable.onboarding_second_screen_icon),
+ contentDescription = LocalContext.current.getString(R.string.app_name),
+ modifier = Modifier
+ .size(200.dp, 300.dp),
+ )
+ Text(
+ text = stringResource(
+ R.string.onboarding_second_screen_title,
+ stringResource(R.string.onboarding_short_app_name),
+ ),
+ modifier = Modifier
+ .padding(top = 32.dp, start = 16.dp, end = 16.dp),
+ textAlign = TextAlign.Center,
+ style = focusTypography.onboardingTitle,
+ )
+ Text(
+ text = stringResource(
+ R.string.onboarding_second_screen_subtitle_one,
+ ),
+ modifier = Modifier
+ .padding(top = 16.dp, start = 16.dp, end = 16.dp),
+ textAlign = TextAlign.Center,
+ style = focusTypography.onboardingSubtitle,
+ )
+ Text(
+ text = stringResource(
+ R.string.onboarding_second_screen_subtitle_two,
+ stringResource(R.string.onboarding_short_app_name),
+ ),
+ modifier = Modifier
+ .padding(top = 16.dp, start = 16.dp, end = 16.dp),
+ textAlign = TextAlign.Center,
+ style = focusTypography.onboardingSubtitle,
+ )
+ ComponentOnBoardingSecondScreenButtons(setAsDefaultBrowser, skipScreen)
+ }
+ }
+}
+
+@Composable
+private fun CloseButton(onCloseButtonClick: () -> Unit) {
+ IconButton(
+ modifier = Modifier
+ .size(48.dp)
+ .background(
+ colorResource(id = R.color.onboardingCloseButtonColor),
+ shape = CircleShape,
+ ),
+ onClick = onCloseButtonClick,
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.mozac_ic_cross_24),
+ contentDescription = stringResource(R.string.onboarding_close_button_content_description),
+ tint = focusColors.closeIcon,
+ )
+ }
+}
+
+@Composable
+private fun ComponentOnBoardingSecondScreenButtons(setAsDefaultBrowser: () -> Unit, skipScreen: () -> Unit) {
+ Button(
+ onClick = setAsDefaultBrowser,
+ modifier = Modifier
+ .padding(top = 33.dp, start = 16.dp, end = 16.dp)
+ .fillMaxWidth(),
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = colorResource(R.color.onboardingButtonOneColor),
+ ),
+ ) {
+ Text(
+ text = AnnotatedString(
+ LocalContext.current.resources.getString(
+ R.string.onboarding_second_screen_default_browser_button_text,
+ ),
+ ),
+ color = PhotonColors.White,
+ )
+ }
+ Button(
+ onClick = skipScreen,
+ modifier = Modifier
+ .padding(top = 8.dp, start = 16.dp, end = 16.dp)
+ .fillMaxWidth(),
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = colorResource(R.color.onboardingButtonTwoColor),
+ ),
+ ) {
+ Text(
+ text = AnnotatedString(
+ LocalContext.current.resources.getString(
+ R.string.onboarding_second_screen_skip_button_text,
+ ),
+ ),
+ color = PhotonColors.Black,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStep.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStep.kt
new file mode 100644
index 0000000000..c53c0f3f62
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStep.kt
@@ -0,0 +1,12 @@
+/* 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.focus.fragment.onboarding
+
+import org.mozilla.focus.R
+
+enum class OnboardingStep(val prefId: Int) {
+ ON_BOARDING_FIRST_SCREEN(R.string.pref_key_first_screen),
+ ON_BOARDING_SECOND_SCREEN(R.string.pref_key_second_screen),
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStorage.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStorage.kt
new file mode 100644
index 0000000000..e2912080cf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingStorage.kt
@@ -0,0 +1,49 @@
+/* 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.focus.fragment.onboarding
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.preference.PreferenceManager
+import org.mozilla.focus.R
+
+class OnboardingStorage(val context: Context) {
+
+ /**
+ * Saves the step of the onBoarding flow.
+ * If the user closes the app at step two he should start again from step two
+ * when he enters again in the app.
+ *
+ * @param onBoardingStep current onBoarding step
+ */
+ internal fun saveCurrentOnboardingStepInSharePref(
+ onBoardingStep: OnboardingStep,
+ ) {
+ val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
+ with(sharedPref.edit()) {
+ putString(
+ context.getString(R.string.pref_key_onboarding_step),
+ context.getString(onBoardingStep.prefId),
+ )
+ apply()
+ }
+ }
+
+ @VisibleForTesting
+ internal fun getCurrentOnboardingStepFromSharedPref(): String {
+ val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
+ return sharedPref.getString(context.getString(R.string.pref_key_onboarding_step), "")
+ ?: ""
+ }
+
+ internal fun getCurrentOnboardingStep() =
+ when (getCurrentOnboardingStepFromSharedPref()) {
+ context.getString(R.string.pref_key_first_screen) -> OnboardingStep.ON_BOARDING_FIRST_SCREEN
+ context.getString(R.string.pref_key_second_screen) -> OnboardingStep.ON_BOARDING_SECOND_SCREEN
+ else -> {
+ OnboardingStep.ON_BOARDING_FIRST_SCREEN
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/input/InputToolbarIntegration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/input/InputToolbarIntegration.kt
new file mode 100644
index 0000000000..a360f68131
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/input/InputToolbarIntegration.kt
@@ -0,0 +1,186 @@
+/* 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.focus.input
+
+import androidx.annotation.VisibleForTesting
+import androidx.appcompat.widget.AppCompatEditText
+import androidx.compose.material.Text
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapNotNull
+import mozilla.components.browser.domains.autocomplete.CustomDomainsProvider
+import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.compose.cfr.CFRPopup
+import mozilla.components.compose.cfr.CFRPopupProperties
+import mozilla.components.concept.toolbar.AutocompleteResult
+import mozilla.components.concept.toolbar.Toolbar
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.fragment.UrlInputFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.ui.theme.focusTypography
+
+class InputToolbarIntegration(
+ private val toolbar: BrowserToolbar,
+ private val fragment: UrlInputFragment,
+ shippedDomainsProvider: ShippedDomainsProvider,
+ customDomainsProvider: CustomDomainsProvider,
+) : LifecycleAwareFeature {
+ private val settings = toolbar.context.settings
+
+ private var useShippedDomainProvider: Boolean = false
+ private var useCustomDomainProvider: Boolean = false
+
+ @VisibleForTesting
+ internal var startBrowsingCfrScope: CoroutineScope? = null
+
+ init {
+ with(toolbar.display) {
+ indicators = emptyList()
+ hint = fragment.resources.getString(R.string.urlbar_hint)
+ colors = toolbar.display.colors.copy(
+ hint = ContextCompat.getColor(toolbar.context, R.color.urlBarHintText),
+ text = ContextCompat.getColor(toolbar.context, R.color.primaryText),
+ )
+ }
+ toolbar.edit.hint = fragment.resources.getString(R.string.urlbar_hint)
+ toolbar.private = true
+ toolbar.edit.colors = toolbar.edit.colors.copy(
+ hint = ContextCompat.getColor(toolbar.context, R.color.urlBarHintText),
+ text = ContextCompat.getColor(toolbar.context, R.color.primaryText),
+ clear = ContextCompat.getColor(toolbar.context, R.color.primaryText),
+ suggestionBackground = ContextCompat.getColor(toolbar.context, R.color.autocompleteBackgroundColor),
+ )
+
+ toolbar.setOnEditListener(
+ object : Toolbar.OnEditListener {
+ override fun onStartEditing() {
+ fragment.onStartEditing()
+ }
+
+ override fun onCancelEditing(): Boolean {
+ fragment.onCancelEditing()
+ return true
+ }
+
+ override fun onTextChanged(text: String) {
+ fragment.onTextChange(text)
+ }
+ },
+ )
+
+ toolbar.setOnUrlCommitListener { url ->
+ fragment.onCommit(url)
+ false
+ }
+
+ toolbar.setAutocompleteListener { text, delegate ->
+ var result: AutocompleteResult? = null
+ if (useCustomDomainProvider) {
+ result = customDomainsProvider.getAutocompleteSuggestion(text)
+ }
+
+ if (useShippedDomainProvider && result == null) {
+ result = shippedDomainsProvider.getAutocompleteSuggestion(text)
+ }
+
+ if (result != null) {
+ delegate.applyAutocompleteResult(
+ AutocompleteResult(
+ result.input,
+ result.text,
+ result.url,
+ result.source,
+ result.totalItems,
+ ),
+ )
+ } else {
+ delegate.noAutocompleteResult(text)
+ }
+ }
+
+ // Use the same background for display/edit modes.
+ val urlBackground = ResourcesCompat.getDrawable(
+ fragment.resources,
+ R.drawable.toolbar_url_background,
+ fragment.context?.theme,
+ )
+
+ toolbar.display.setUrlBackground(urlBackground)
+ toolbar.edit.setUrlBackground(urlBackground)
+ }
+
+ override fun start() {
+ useCustomDomainProvider = settings.shouldAutocompleteFromCustomDomainList()
+ useShippedDomainProvider = settings.shouldAutocompleteFromShippedDomainList()
+ if (fragment.components?.appStore?.state?.showStartBrowsingTabsCfr == true) {
+ observeStartBrowserCfrVisibility()
+ }
+ }
+
+ @VisibleForTesting
+ internal fun observeStartBrowserCfrVisibility() {
+ startBrowsingCfrScope = fragment.components?.appStore?.flowScoped { flow ->
+ flow.mapNotNull { state -> state.showStartBrowsingTabsCfr }
+ .distinctUntilChanged()
+ .collect { showStartBrowsingCfr ->
+ if (showStartBrowsingCfr) {
+ CFRPopup(
+ anchor = toolbar.findViewById(R.id.mozac_browser_toolbar_background),
+ properties = CFRPopupProperties(
+ popupWidth = 256.dp,
+ popupAlignment = CFRPopup.PopupAlignment.BODY_TO_ANCHOR_START,
+ popupBodyColors = listOf(
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_end_color,
+ ),
+ ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cfr_pop_up_shape_start_color,
+ ),
+ ),
+ dismissButtonColor = ContextCompat.getColor(
+ fragment.requireContext(),
+ R.color.cardview_light_background,
+ ),
+ popupVerticalOffset = 0.dp,
+ ),
+ onDismiss = {
+ onDismissStartBrowsingCfr()
+ },
+ text = {
+ Text(
+ style = focusTypography.cfrTextStyle,
+ text = fragment.resources.getString(R.string.cfr_for_start_browsing),
+ color = colorResource(R.color.cfr_text_color),
+ )
+ },
+ ).apply {
+ show()
+ }
+ }
+ }
+ }
+ }
+
+ private fun onDismissStartBrowsingCfr() {
+ fragment.components?.appStore?.dispatch(AppAction.ShowStartBrowsingCfrChange(false))
+ fragment.requireContext().settings.shouldShowStartBrowsingCfr = false
+ }
+
+ override fun stop() {
+ startBrowsingCfrScope?.cancel()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/LocaleManager.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/LocaleManager.kt
new file mode 100644
index 0000000000..eb435b5f76
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/LocaleManager.kt
@@ -0,0 +1,80 @@
+/* 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.focus.locale
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import org.mozilla.focus.R
+import org.mozilla.focus.generated.LocalesList
+import java.util.Locale
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * This class manages persistence, application, and otherwise handling of
+ * user-specified locales.
+ *
+ * Of note:
+ *
+ * * It's a singleton, because its scope extends to that of the application,
+ * and definitionally all changes to the locale of the app must go through
+ * this.
+ * * It's lazy.
+ * * It relies on using the SharedPreferences file owned by the app for performance.
+ */
+class LocaleManager {
+ // These are volatile because we don't impose restrictions over which thread calls our methods.
+ @Volatile
+ private var currentLocale: Locale? = null
+
+ private fun getSharedPreferences(context: Context): SharedPreferences {
+ if (PREF_LOCALE == null) {
+ PREF_LOCALE = context.resources.getString(R.string.pref_key_locale)
+ }
+
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ }
+
+ /**
+ * @return the persisted locale in Java format: "en_US".
+ */
+ private fun getPersistedLocale(context: Context): String? {
+ val settings: SharedPreferences = getSharedPreferences(context)
+ return settings.getString(PREF_LOCALE, null)
+ }
+
+ /**
+ * @return the current locale in Java format: "en_US".
+ */
+ fun getCurrentLocale(context: Context): Locale? {
+ if (currentLocale != null) {
+ return currentLocale
+ }
+
+ val current = getPersistedLocale(context) ?: return null
+ return Locales.parseLocaleCode(current).also { currentLocale = it }
+ }
+
+ companion object {
+ private var PREF_LOCALE: String? = null
+ val instance = AtomicReference()
+ get() {
+ var localeManager = field.get()
+ if (localeManager != null) {
+ return field
+ }
+ localeManager = LocaleManager()
+ return if (field.compareAndSet(null, localeManager)) {
+ AtomicReference(localeManager)
+ } else {
+ field
+ }
+ }
+
+ /**
+ * Returns a list of supported locale codes
+ */
+ val packagedLocaleTags: Collection = LocalesList.BUNDLED_LOCALES
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/Locales.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/Locales.kt
new file mode 100644
index 0000000000..4691b6886d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/Locales.kt
@@ -0,0 +1,107 @@
+/* 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.focus.locale
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import mozilla.components.support.ktx.android.content.res.locale
+import org.mozilla.focus.locale.LocaleManager.Companion.instance
+import java.util.Locale
+
+/**
+ * This is a helper class to do typical locale switching operations without
+ * hitting StrictMode errors or adding boilerplate to common activity
+ * subclasses.
+ *
+ * Inherit from `LocaleAwareFragmentActivity` or `LocaleAwareActivity`.
+ */
+object Locales {
+ /**
+ * Sometimes we want just the language for a locale, not the entire language
+ * tag. But Java's .getLanguage method is wrong.
+ *
+ * This method is equivalent to the first part of
+ * [Locales.getLanguageTag].
+ *
+ * @return a language string, such as "he" for the Hebrew locales.
+ */
+ fun getLanguage(locale: Locale): String {
+ // Modernize certain language codes.
+ return when (val language = locale.language) {
+ "iw" -> {
+ "he"
+ }
+
+ "in" -> {
+ "id"
+ }
+
+ "ji" -> {
+ "yi"
+ }
+
+ else -> language
+ }
+ }
+
+ /**
+ * Gecko uses locale codes like "es-ES", whereas a Java [Locale]
+ * stringifies as "es_ES".
+ *
+ * This method approximates the Java 7 method
+ * `Locale#toLanguageTag()`.
+ *
+ * @return a locale string suitable for passing to Gecko.
+ */
+ @JvmStatic
+ fun getLanguageTag(locale: Locale): String {
+ val language = getLanguage(locale)
+ val country = locale.country
+
+ return if (country.isEmpty()) {
+ language
+ } else {
+ "$language-$country"
+ }
+ }
+
+ /**
+ * Parses a locale code [String] and returns the corresponding [Locale].
+ */
+ fun parseLocaleCode(localeCode: String): Locale {
+ var index: Int
+
+ if (localeCode.indexOf('-').also { index = it } != -1 ||
+ localeCode.indexOf('_').also { index = it } != -1
+ ) {
+ val langCode = localeCode.substring(0, index)
+ val countryCode = localeCode.substring(index + 1)
+ return Locale(langCode, countryCode)
+ }
+
+ return Locale(localeCode)
+ }
+
+ /**
+ * Get a Resources instance with the currently selected locale applied.
+ */
+ fun getLocalizedResources(context: Context): Resources {
+ val currentResources = context.resources
+ val currentLocale: Locale? = instance.get()?.getCurrentLocale(context)
+ val viewLocale = currentResources.locale
+
+ if (currentLocale == null) {
+ return currentResources
+ }
+
+ if (currentLocale.toLanguageTag() == viewLocale.toLanguageTag()) {
+ return currentResources
+ }
+
+ val configuration = Configuration(currentResources.configuration)
+ configuration.setLocale(currentLocale)
+ return context.createConfigurationContext(configuration).resources
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/DefaultLanguageScreenInteractor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/DefaultLanguageScreenInteractor.kt
new file mode 100644
index 0000000000..0d0bd76248
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/DefaultLanguageScreenInteractor.kt
@@ -0,0 +1,17 @@
+/* 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.focus.locale.screen
+
+class DefaultLanguageScreenInteractor(
+ private val languageScreenStore: LanguageScreenStore,
+) {
+
+ fun handleLanguageSelected(language: Language) {
+ if (languageScreenStore.state.selectedLanguage == language) {
+ return
+ }
+ languageScreenStore.dispatch(LanguageScreenAction.Select(selectedLanguage = language))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/Language.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/Language.kt
new file mode 100644
index 0000000000..dd72f42fc1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/Language.kt
@@ -0,0 +1,15 @@
+/* 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.focus.locale.screen
+
+/**
+ * Data class for Language that comes from the LanguageStorage
+ *
+ * @param displayName of the Language the will be shown in the GUI
+ * @param tag of the Language that will be saved in SharePref if the element is selected
+ * @param index of the Language in the list .It is used for auto-scrolling to the current selected
+ * Language
+ */
+data class Language(var displayName: String?, val tag: String, val index: Int)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageFragment.kt
new file mode 100644
index 0000000000..98ff11113b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageFragment.kt
@@ -0,0 +1,115 @@
+/* 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.focus.locale.screen
+
+import android.os.Bundle
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.lib.state.ext.observeAsComposableState
+import mozilla.components.support.locale.LocaleUseCases
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.settings.BaseComposeFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+
+class LanguageFragment : BaseComposeFragment() {
+ private lateinit var browserStore: BrowserStore
+ private lateinit var localeUseCases: LocaleUseCases
+ private lateinit var languageScreenStore: LanguageScreenStore
+ private lateinit var defaultLanguageScreenInteractor: DefaultLanguageScreenInteractor
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ browserStore = requireContext().components.store
+ localeUseCases = LocaleUseCases(browserStore)
+ languageScreenStore = LanguageScreenStore(
+ LanguageScreenState(),
+ listOf(
+ LanguageMiddleware(
+ activity = requireActivity(),
+ localeUseCase = localeUseCases,
+ ),
+ ),
+ )
+ defaultLanguageScreenInteractor = DefaultLanguageScreenInteractor(
+ languageScreenStore = languageScreenStore,
+ )
+ }
+
+ override val titleRes: Int
+ get() = R.string.preference_language
+
+ override fun onNavigateUp(): () -> Unit {
+ return {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.General),
+ )
+ }
+ }
+
+ @Composable
+ override fun Content() {
+ val languagesList = languageScreenStore
+ .observeAsComposableState { state -> state.languageList }.value
+ val languageSelected = languageScreenStore.observeAsComposableState { state ->
+ state.selectedLanguage
+ }.value
+ if (languageSelected != null && languagesList != null) {
+ Languages(languageSelected = languageSelected, languages = languagesList)
+ }
+ }
+
+ private fun createLanguageListItem(
+ languages: List,
+ state: MutableState,
+ ): List {
+ val languageListItems = ArrayList()
+ languages.forEach { language ->
+ if (language.tag == LanguageStorage.LOCALE_SYSTEM_DEFAULT) {
+ language.displayName = context?.getString(R.string.preference_language_systemdefault)
+ }
+ val languageListItem = LanguageListItem(
+ language = language,
+ onClick = {
+ state.value = language.tag
+ defaultLanguageScreenInteractor.handleLanguageSelected(language)
+ },
+ )
+ languageListItems.add(languageListItem)
+ }
+ return languageListItems
+ }
+
+ @Composable
+ private fun Languages(languageSelected: Language, languages: List) {
+ val listState = rememberLazyListState()
+ val coroutineScope = rememberCoroutineScope()
+ LaunchedEffect(languageSelected.index) {
+ coroutineScope.launch {
+ languageSelected.let { listState.scrollToItem(it.index) }
+ }
+ }
+ val state = remember {
+ mutableStateOf(languageSelected.tag)
+ }
+ LanguagesList(
+ languageListItems = createLanguageListItem(
+ languages = languages,
+ state = state,
+ ),
+ state = state,
+ listState = listState,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageListItem.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageListItem.kt
new file mode 100644
index 0000000000..e5708ffe6c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageListItem.kt
@@ -0,0 +1,16 @@
+/* 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.focus.locale.screen
+
+/**
+ * Data class for the LanguageListItem that goes to the compose ListView
+ *
+ * @param language item to be display in ListView
+ * @param onClick Callback when the user taps on Language Item
+ */
+data class LanguageListItem(
+ val language: Language,
+ val onClick: (String) -> Unit,
+)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageMiddleware.kt
new file mode 100644
index 0000000000..89043c17b5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageMiddleware.kt
@@ -0,0 +1,69 @@
+/* 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.focus.locale.screen
+
+import android.app.Activity
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import mozilla.components.support.locale.LocaleManager
+import mozilla.components.support.locale.LocaleUseCases
+import org.mozilla.focus.locale.Locales
+import org.mozilla.focus.settings.InstalledSearchEnginesSettingsFragment
+import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
+import java.util.Locale
+
+class LanguageMiddleware(val activity: Activity, private val localeUseCase: LocaleUseCases) :
+ Middleware {
+
+ private val storage by lazy {
+ LanguageStorage(context = activity)
+ }
+
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (LanguageScreenAction) -> Unit,
+ action: LanguageScreenAction,
+ ) {
+ when (action) {
+ is LanguageScreenAction.Select -> {
+ storage.saveCurrentLanguageInSharePref(action.selectedLanguage.tag)
+ setCurrentLanguage(action.selectedLanguage.tag)
+ next(action)
+ }
+ is LanguageScreenAction.InitLanguages -> {
+ /**
+ * The initial LanguageScreenState when the user enters first in the screen
+ */
+ context.dispatch(
+ LanguageScreenAction.UpdateLanguages(
+ storage.getLanguages(),
+ storage.getSelectedLanguageTag(),
+ ),
+ )
+ }
+ else -> {
+ next(action)
+ }
+ }
+ }
+
+ /**
+ * It changes the system defined locale to the indicated Language .
+ * It recreates the current activity for changes to take effect.
+ *
+ * @param languageTag selected Language Tag that comes from Language object
+ */
+ private fun setCurrentLanguage(languageTag: String) {
+ InstalledSearchEnginesSettingsFragment.languageChanged = true
+ val locale: Locale?
+ if (languageTag == LanguageStorage.LOCALE_SYSTEM_DEFAULT) {
+ LocaleManager.resetToSystemDefault(activity, localeUseCase)
+ } else {
+ locale = Locales.parseLocaleCode(languageTag)
+ LocaleManager.setNewLocale(activity, localeUseCase, locale)
+ }
+ runOnUiThread { activity.recreate() }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageScreenStore.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageScreenStore.kt
new file mode 100644
index 0000000000..ce43e7897e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageScreenStore.kt
@@ -0,0 +1,70 @@
+/* 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.focus.locale.screen
+
+import mozilla.components.lib.state.Action
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.State
+import mozilla.components.lib.state.Store
+
+class LanguageScreenStore(
+ initialState: LanguageScreenState,
+ middlewares: List> = emptyList(),
+) : Store(
+ initialState,
+ ::languagesScreenStateReducer,
+ middlewares,
+) {
+ init {
+ dispatch(LanguageScreenAction.InitLanguages)
+ }
+}
+
+/**
+ * The state of the language selection screen
+ *
+ * @property languageList The full list of languages available
+ * @property selectedLanguage The current selected language
+ */
+data class LanguageScreenState(
+ val languageList: List = emptyList(),
+ val selectedLanguage: Language? = null,
+) : State
+
+/**
+ * Action to dispatch through the `LanguageScreenStore` to modify `LanguageScreenState` through the reducer.
+ */
+sealed class LanguageScreenAction : Action {
+ object InitLanguages : LanguageScreenAction()
+ data class Select(val selectedLanguage: Language) : LanguageScreenAction()
+ data class UpdateLanguages(
+ val languageList: List,
+ val selectedLanguage: Language,
+ ) : LanguageScreenAction()
+}
+
+/**
+ * Reduces the language state from the current state and an action performed on it.
+ *
+ * @param state the current locale state
+ * @param action the action to perform
+ * @return the new locale state
+ */
+private fun languagesScreenStateReducer(
+ state: LanguageScreenState,
+ action: LanguageScreenAction,
+): LanguageScreenState {
+ return when (action) {
+ is LanguageScreenAction.Select -> {
+ state.copy(selectedLanguage = action.selectedLanguage)
+ }
+ is LanguageScreenAction.UpdateLanguages -> {
+ state.copy(languageList = action.languageList, selectedLanguage = action.selectedLanguage)
+ }
+ LanguageScreenAction.InitLanguages -> {
+ throw IllegalStateException("You need to add LanguageMiddleware to your LanguageScreenStore. ($action)")
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageStorage.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageStorage.kt
new file mode 100644
index 0000000000..5788dedf0c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LanguageStorage.kt
@@ -0,0 +1,90 @@
+/* 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.focus.locale.screen
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.focus.R
+import org.mozilla.focus.locale.LocaleManager
+import java.util.Arrays
+
+class LanguageStorage(private val context: Context) {
+
+ /**
+ * Returns the current selected Language object or System default Language if nothing is selected
+ */
+ fun getSelectedLanguageTag(): Language {
+ val sharedConfig: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+ val languageTag = sharedConfig.getString(
+ context.resources.getString(R.string.pref_key_locale),
+ LOCALE_SYSTEM_DEFAULT,
+ ) ?: LOCALE_SYSTEM_DEFAULT
+ for (language in getLanguages()) {
+ if (languageTag == language.tag) {
+ return language
+ }
+ }
+ return Language(context.getString(R.string.preference_language_systemdefault), LOCALE_SYSTEM_DEFAULT, 0)
+ }
+
+ /**
+ * Returns The full list of languages available.System default Language will be the first item in the list .
+ */
+ fun getLanguages(): List {
+ val listLocaleNameAndTag = ArrayList()
+ val descriptors = getUsableLocales()
+ listLocaleNameAndTag.add(
+ Language(
+ context.getString(
+ R.string.preference_language_systemdefault,
+ ),
+ LOCALE_SYSTEM_DEFAULT,
+ 0,
+ ),
+ )
+ descriptors.indices.forEach { i ->
+ val displayName = descriptors[i]!!.getNativeName()
+ val tag = descriptors[i]!!.getTag()
+ Logger.info("$displayName => $tag ")
+ listLocaleNameAndTag.add(Language(displayName = displayName, tag = tag, index = i + 1))
+ }
+ return listLocaleNameAndTag
+ }
+
+ /**
+ * Saves the current selected language tag
+ *
+ * @property languageTag the tag of the language
+ */
+ fun saveCurrentLanguageInSharePref(languageTag: String) {
+ val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
+ with(sharedPref.edit()) {
+ putString(context.getString(R.string.pref_key_locale), languageTag)
+ apply()
+ }
+ }
+
+ /**
+ * This method generates the descriptor array.
+ */
+ private fun getUsableLocales(): Array {
+ val shippingLocales = LocaleManager.packagedLocaleTags
+ val initialCount: Int = shippingLocales.size
+ val locales: MutableSet = HashSet(initialCount)
+ for (tag in shippingLocales) {
+ locales.add(LocaleDescriptor(tag))
+ }
+ val usableCount = locales.size
+ val descriptors: Array = locales.toTypedArray()
+ Arrays.sort(descriptors, 0, usableCount)
+ return descriptors
+ }
+
+ companion object {
+ const val LOCALE_SYSTEM_DEFAULT = "LOCALE_SYSTEM_DEFAULT"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleDescriptor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleDescriptor.kt
new file mode 100644
index 0000000000..34c2fdb8e7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleDescriptor.kt
@@ -0,0 +1,125 @@
+/* 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.focus.locale.screen
+
+import android.text.TextUtils
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.focus.locale.Locales
+import java.text.Collator
+import java.util.Locale
+
+class LocaleDescriptor(private val localeTag: String) : Comparable {
+
+ private val languageCodeAndNameMap: HashMap = HashMap()
+ private var nativeName: String? = null
+
+ init {
+ fillLanguageCodeAndNameMap()
+ setupLocaleDescriptor()
+ }
+
+ private fun fillLanguageCodeAndNameMap() {
+ // Only ICU 57 actually contains the Asturian name for Asturian, even Android 7.1 is still
+ // shipping with ICU 56, so we need to override the Asturian name (otherwise displayName will
+ // be the current locales version of Asturian, see:
+ // https://github.com/mozilla-mobile/focus-android/issues/634#issuecomment-303886118
+ languageCodeAndNameMap["ast"] = "Asturianu"
+ // On an Android 8.0 device those languages are not known and we need to add the names
+ // manually. Loading the resources at runtime works without problems though.
+ languageCodeAndNameMap["ace"] = "Acehnese"
+ languageCodeAndNameMap["an"] = "Aragonés"
+ languageCodeAndNameMap["anp"] = "अंगिका"
+ languageCodeAndNameMap["ay"] = "Aimara"
+ languageCodeAndNameMap["cak"] = "Kaqchikel"
+ languageCodeAndNameMap["co"] = "Corsu"
+ languageCodeAndNameMap["hus"] = "Tének"
+ languageCodeAndNameMap["ia"] = "Interlingua"
+ languageCodeAndNameMap["ixl"] = "Ixil"
+ languageCodeAndNameMap["jv"] = "Basa Jawa"
+ languageCodeAndNameMap["meh"] = "Tu´un savi ñuu Yasi'í Yuku Iti"
+ languageCodeAndNameMap["mix"] = "Tu'un savi"
+ languageCodeAndNameMap["nv"] = "Navajo"
+ languageCodeAndNameMap["oc"] = "occitan"
+ languageCodeAndNameMap["pai"] = "Paa ipai"
+ languageCodeAndNameMap["ppl"] = "Náhuat Pipil"
+ languageCodeAndNameMap["quc"] = "K'iche'"
+ languageCodeAndNameMap["quy"] = "Chanka Qhichwa"
+ languageCodeAndNameMap["skr"] = "سرائیکی"
+ languageCodeAndNameMap["sn"] = "ChiShona"
+ languageCodeAndNameMap["su"] = "Basa Sunda"
+ languageCodeAndNameMap["trs"] = "Triqui"
+ languageCodeAndNameMap["tsz"] = "P'urhepecha"
+ languageCodeAndNameMap["tt"] = "татарча"
+ languageCodeAndNameMap["wo"] = "Wolof"
+ languageCodeAndNameMap["zam"] = "DíɁztè"
+ languageCodeAndNameMap["zh-CN"] = "中文 (中国大陆)"
+ }
+
+ private fun setupLocaleDescriptor() {
+ val locale = Locales.parseLocaleCode(localeTag)
+ val displayName: String? = getDisplayName(locale)
+
+ if (TextUtils.isEmpty(displayName)) {
+ // There's nothing sane we can do.
+ Logger.error("Display name is empty. Using $locale")
+ nativeName = locale.toString()
+ return
+ }
+
+ val directionality = Character.getDirectionality(displayName!![0])
+ if (directionality == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
+ var firstLetter = displayName.substring(0, 1)
+
+ // Android OS creates an instance of Transliterator to convert the first letter
+ // of the Greek locale. See CaseMapper.toUpperCase(Locale locale, String s, int count)
+ // Since it's already in upper case, we don't need it
+ if (!Character.isUpperCase(firstLetter[0])) {
+ firstLetter = firstLetter.uppercase(locale)
+ }
+ nativeName = firstLetter + displayName.substring(1)
+ return
+ }
+ nativeName = displayName
+ }
+
+ private fun getDisplayName(locale: Locale): String? {
+ return when {
+ languageCodeAndNameMap.containsKey(locale.language) -> {
+ languageCodeAndNameMap[locale.language]
+ }
+ languageCodeAndNameMap.containsKey(locale.toLanguageTag()) -> {
+ languageCodeAndNameMap[locale.toLanguageTag()]
+ }
+ else -> {
+ locale.getDisplayName(locale)
+ }
+ }
+ }
+
+ fun getTag(): String {
+ return localeTag
+ }
+
+ fun getNativeName(): String? {
+ return nativeName
+ }
+
+ override fun hashCode(): Int {
+ return localeTag.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is LocaleDescriptor && compareTo(other) == 0
+ }
+
+ override operator fun compareTo(other: LocaleDescriptor): Int {
+ // We sort by name, so we use Collator.
+ return COLLATOR.compare(nativeName, other.nativeName)
+ }
+
+ companion object {
+ private val COLLATOR = Collator.getInstance(Locale.US)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleFragmentCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleFragmentCompose.kt
new file mode 100644
index 0000000000..2d6089718b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/locale/screen/LocaleFragmentCompose.kt
@@ -0,0 +1,175 @@
+/* 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.focus.locale.screen
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.RadioButton
+import androidx.compose.material.RadioButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import kotlinx.coroutines.launch
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+
+private fun getFakeLanguages(): List {
+ return mutableListOf().apply {
+ var index = 0
+ add(LanguageListItem(Language("Română", "ro", index++), onClick = {}))
+ add(LanguageListItem(Language("Slovenčina", "sk", index++), onClick = {}))
+ add(LanguageListItem(Language("Português (Brasil)", "pt-BR", index++), onClick = {}))
+ add(LanguageListItem(Language("Nederlands", "nl", index++), onClick = {}))
+ add(LanguageListItem(Language("Magyar", "hu", index++), onClick = {}))
+ add(LanguageListItem(Language("Lietuvių", "lt", ++index), onClick = {}))
+ }
+}
+
+@Composable
+@Preview
+private fun LanguagesListComposablePreview() {
+ FocusTheme {
+ val listState = rememberLazyListState()
+ val coroutineScope = rememberCoroutineScope()
+ LaunchedEffect(0) {
+ coroutineScope.launch {
+ listState.scrollToItem(0)
+ }
+ }
+ val state = remember {
+ mutableStateOf("ro")
+ }
+ LanguagesList(
+ languageListItems = getFakeLanguages(),
+ state = state,
+ listState = listState,
+ )
+ }
+}
+
+/**
+ * Displays a list of Languages in a listView
+ *
+ * @param languageListItems The list of Languages items to be displayed.
+ * @param state the current Selected Language
+ * @param listState scrolls to the current selected Language
+ */
+@Composable
+fun LanguagesList(
+ languageListItems: List,
+ state: MutableState,
+ listState: LazyListState,
+) {
+ FocusTheme {
+ LazyColumn(
+ modifier = Modifier.background(colorResource(R.color.settings_background), shape = RectangleShape),
+ state = listState,
+ contentPadding = PaddingValues(horizontal = 12.dp),
+ ) {
+ items(languageListItems) { item ->
+ LanguageNameAndTagItem(
+ language = item.language,
+ isSelected = state.value == item.language.tag,
+ onClick = item.onClick,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun LanguageNameAndTagItem(
+ language: Language,
+ isSelected: Boolean,
+ onClick: (String) -> Unit,
+) {
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ LanguageRadioButton(
+ language = language,
+ isSelected = isSelected,
+ onClick = onClick,
+ )
+ Spacer(modifier = Modifier.width(18.dp))
+ language.displayName?.let {
+ LanguageDisplayName(
+ language = language,
+ onClick = onClick,
+ )
+ }
+ }
+}
+
+/**
+ * Displays a single language radiobutton
+ *
+ * @param language of the item
+ * @param isSelected check or uncheck the radioButton if the language is selected or not
+ * @param onClick Callback when the user taps on Language
+ */
+@Composable
+private fun LanguageRadioButton(
+ language: Language,
+ isSelected: Boolean,
+ onClick: (String) -> Unit,
+) {
+ RadioButton(
+ selected = isSelected,
+ colors = RadioButtonDefaults.colors(selectedColor = focusColors.radioButtonSelected),
+ onClick = {
+ onClick(language.tag)
+ },
+ )
+}
+
+/**
+ * Displays a single language Text
+ *
+ * @param language of the item to be display in the textView
+ * @param onClick Callback when the user taps on Language text , the same like on the radioButton
+ */
+@Composable
+private fun LanguageDisplayName(language: Language, onClick: (String) -> Unit) {
+ Text(
+ text = AnnotatedString(language.displayName!!),
+ style = TextStyle(
+ fontSize = 20.sp,
+ ),
+ modifier = Modifier
+ .padding(10.dp)
+ .clickable { onClick(language.tag) },
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/media/MediaSessionService.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/media/MediaSessionService.kt
new file mode 100644
index 0000000000..e966596dba
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/media/MediaSessionService.kt
@@ -0,0 +1,20 @@
+/* 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.focus.media
+
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.base.crash.CrashReporting
+import mozilla.components.feature.media.service.AbstractMediaSessionService
+import mozilla.components.support.base.android.NotificationsDelegate
+import org.mozilla.focus.ext.components
+
+/**
+ * [AbstractMediaSessionService] implementation for injecting [BrowserStore] singleton.
+ */
+class MediaSessionService : AbstractMediaSessionService() {
+ override val crashReporter: CrashReporting? by lazy { components.crashReporter }
+ override val store: BrowserStore by lazy { components.store }
+ override val notificationsDelegate: NotificationsDelegate by lazy { components.notificationsDelegate }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/ToolbarMenu.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/ToolbarMenu.kt
new file mode 100644
index 0000000000..4f9690b12a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/ToolbarMenu.kt
@@ -0,0 +1,40 @@
+/* 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.focus.menu
+
+import mozilla.components.browser.menu.BrowserMenuBuilder
+import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
+
+interface ToolbarMenu {
+
+ sealed class Item {
+ data class RequestDesktop(val isChecked: Boolean) : Item()
+ object Reload : Item()
+ object Back : Item()
+ object Forward : Item()
+ object Share : Item()
+ object AddToShortcuts : Item()
+ object RemoveFromShortcuts : Item()
+ object FindInPage : Item()
+ object AddToHomeScreen : Item()
+ object OpenInApp : Item()
+ object Settings : Item()
+ object Stop : Item()
+ }
+
+ sealed class CustomTabItem {
+ data class RequestDesktop(val isChecked: Boolean) : Item()
+ object Reload : Item()
+ object Stop : Item()
+ object Back : Item()
+ object Forward : Item()
+ object FindInPage : Item()
+ object AddToHomeScreen : Item()
+ object OpenInBrowser : Item()
+ object OpenInApp : Item()
+ }
+
+ val menuBuilder: BrowserMenuBuilder
+ val menuToolbar: BrowserMenuItemToolbar
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/CustomTabMenu.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/CustomTabMenu.kt
new file mode 100644
index 0000000000..c6f79f6468
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/CustomTabMenu.kt
@@ -0,0 +1,160 @@
+/* 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.focus.menu.browser
+
+import android.content.Context
+import android.graphics.Typeface
+import mozilla.components.browser.menu.WebExtensionBrowserMenuBuilder
+import mozilla.components.browser.menu.item.BrowserMenuCategory
+import mozilla.components.browser.menu.item.BrowserMenuDivider
+import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
+import mozilla.components.browser.menu.item.BrowserMenuImageText
+import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
+import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
+import mozilla.components.browser.menu.item.WebExtensionPlaceholderMenuItem
+import mozilla.components.browser.state.selector.findCustomTab
+import mozilla.components.browser.state.state.CustomTabSessionState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
+import org.mozilla.focus.R
+import org.mozilla.focus.menu.ToolbarMenu
+import org.mozilla.focus.theme.resolveAttribute
+
+class CustomTabMenu(
+ private val context: Context,
+ private val store: BrowserStore,
+ private val currentTabId: String,
+ private val onItemTapped: (ToolbarMenu.Item) -> Unit = {},
+) : ToolbarMenu {
+
+ private val selectedSession: CustomTabSessionState?
+ get() = store.state.findCustomTab(currentTabId)
+
+ override val menuBuilder by lazy {
+ WebExtensionBrowserMenuBuilder(
+ items = menuItems,
+ store = store,
+ )
+ }
+
+ override val menuToolbar by lazy {
+ val back = BrowserMenuItemToolbar.TwoStateButton(
+ primaryImageResource = R.drawable.mozac_ic_back_24,
+ primaryContentDescription = context.getString(R.string.content_description_back),
+ primaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ isInPrimaryState = {
+ selectedSession?.content?.canGoBack ?: false
+ },
+ secondaryImageTintResource = context.theme.resolveAttribute(R.attr.disabled),
+ disableInSecondaryState = true,
+ longClickListener = { onItemTapped.invoke(ToolbarMenu.CustomTabItem.Back) },
+ ) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.Back)
+ }
+
+ val forward = BrowserMenuItemToolbar.TwoStateButton(
+ primaryImageResource = R.drawable.mozac_ic_forward_24,
+ primaryContentDescription = context.getString(R.string.content_description_forward),
+ primaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ isInPrimaryState = {
+ selectedSession?.content?.canGoForward ?: true
+ },
+ secondaryImageTintResource = context.theme.resolveAttribute(R.attr.disabled),
+ disableInSecondaryState = true,
+ longClickListener = { onItemTapped.invoke(ToolbarMenu.CustomTabItem.Forward) },
+ ) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.Forward)
+ }
+
+ val refresh = BrowserMenuItemToolbar.TwoStateButton(
+ primaryImageResource = R.drawable.mozac_ic_arrow_clockwise_24,
+ primaryContentDescription = context.getString(R.string.content_description_reload),
+ primaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ isInPrimaryState = {
+ selectedSession?.content?.loading == false
+ },
+ secondaryImageResource = R.drawable.mozac_ic_stop,
+ secondaryContentDescription = context.getString(R.string.content_description_stop),
+ secondaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ disableInSecondaryState = false,
+ longClickListener = { onItemTapped.invoke(ToolbarMenu.CustomTabItem.Reload) },
+ ) {
+ if (selectedSession?.content?.loading == true) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.Stop)
+ } else {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.Reload)
+ }
+ }
+ BrowserMenuItemToolbar(listOf(back, forward, refresh))
+ }
+
+ private val menuItems by lazy {
+ val findInPage = BrowserMenuImageText(
+ label = context.getString(R.string.find_in_page),
+ imageResource = R.drawable.mozac_ic_search_24,
+ ) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.FindInPage)
+ }
+
+ val desktopMode = BrowserMenuImageSwitch(
+ imageResource = R.drawable.mozac_ic_device_desktop_24,
+ label = context.getString(R.string.preference_performance_request_desktop_site2),
+ initialState = {
+ selectedSession?.content?.desktopMode ?: true
+ },
+ ) { checked ->
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.RequestDesktop(checked))
+ }
+
+ val reportSiteIssue = WebExtensionPlaceholderMenuItem(
+ id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID,
+ iconTintColorResource = context.theme.resolveAttribute(R.attr.primaryText),
+ )
+
+ val addToHomescreen = BrowserMenuImageText(
+ label = context.getString(R.string.menu_add_to_home_screen),
+ imageResource = R.drawable.mozac_ic_add_to_homescreen_24,
+ ) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.AddToHomeScreen)
+ }
+
+ val appName = context.getString(R.string.app_name)
+ val openInFocus = SimpleBrowserMenuItem(
+ label = context.getString(R.string.menu_open_with_default_browser2, appName),
+ ) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.OpenInBrowser)
+ }
+
+ val openInApp = SimpleBrowserMenuItem(
+ label = context.getString(R.string.menu_open_with_a_browser2),
+ ) {
+ onItemTapped.invoke(ToolbarMenu.CustomTabItem.OpenInApp)
+ }
+
+ val poweredBy = BrowserMenuCategory(
+ label = context.getString(R.string.menu_custom_tab_branding, context.getString(R.string.app_name)),
+ textSize = CAPTION_TEXT_SIZE,
+ textColorResource = context.theme.resolveAttribute(R.attr.secondaryText),
+ backgroundColorResource = context.theme.resolveAttribute(R.attr.colorPrimary),
+ textStyle = Typeface.NORMAL,
+ )
+
+ listOfNotNull(
+ menuToolbar,
+ BrowserMenuDivider(),
+ findInPage,
+ desktopMode,
+ reportSiteIssue,
+ BrowserMenuDivider(),
+ addToHomescreen,
+ openInFocus,
+ openInApp,
+ poweredBy,
+ )
+ }
+
+ companion object {
+ private const val CAPTION_TEXT_SIZE = 12f
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/DefaultBrowserMenu.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/DefaultBrowserMenu.kt
new file mode 100644
index 0000000000..f78f44ab7c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/browser/DefaultBrowserMenu.kt
@@ -0,0 +1,193 @@
+/* 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.focus.menu.browser
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import mozilla.components.browser.menu.WebExtensionBrowserMenuBuilder
+import mozilla.components.browser.menu.item.BrowserMenuDivider
+import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
+import mozilla.components.browser.menu.item.BrowserMenuImageText
+import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
+import mozilla.components.browser.menu.item.TwoStateBrowserMenuImageText
+import mozilla.components.browser.menu.item.WebExtensionPlaceholderMenuItem
+import mozilla.components.browser.state.selector.selectedTab
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
+import org.mozilla.focus.R
+import org.mozilla.focus.menu.ToolbarMenu
+import org.mozilla.focus.state.AppStore
+import org.mozilla.focus.theme.resolveAttribute
+import org.mozilla.focus.topsites.DefaultTopSitesStorage.Companion.TOP_SITES_MAX_LIMIT
+
+/**
+ * The overflow menu shown in the BrowserFragment containing page actions like "Refresh", "Share" etc.
+ */
+class DefaultBrowserMenu(
+ private val context: Context,
+ private val appStore: AppStore,
+ private val store: BrowserStore,
+ private val isPinningSupported: Boolean,
+ private val onItemTapped: (ToolbarMenu.Item) -> Unit = {},
+) : ToolbarMenu {
+
+ private val selectedSession: TabSessionState?
+ get() = store.state.selectedTab
+
+ override val menuBuilder by lazy {
+ WebExtensionBrowserMenuBuilder(
+ items = mvpMenuItems,
+ store = store,
+ showAddonsInMenu = false,
+ )
+ }
+
+ override val menuToolbar by lazy {
+ val back = BrowserMenuItemToolbar.TwoStateButton(
+ primaryImageResource = R.drawable.mozac_ic_back_24,
+ primaryContentDescription = context.getString(R.string.content_description_back),
+ primaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ isInPrimaryState = {
+ selectedSession?.content?.canGoBack ?: true
+ },
+ secondaryImageTintResource = context.theme.resolveAttribute(R.attr.disabled),
+ disableInSecondaryState = true,
+ longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Back) },
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.Back)
+ }
+
+ val forward = BrowserMenuItemToolbar.TwoStateButton(
+ primaryImageResource = R.drawable.mozac_ic_forward_24,
+ primaryContentDescription = context.getString(R.string.content_description_forward),
+ primaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ isInPrimaryState = {
+ selectedSession?.content?.canGoForward ?: true
+ },
+ secondaryImageTintResource = context.theme.resolveAttribute(R.attr.disabled),
+ disableInSecondaryState = true,
+ longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward) },
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.Forward)
+ }
+
+ val refresh = BrowserMenuItemToolbar.TwoStateButton(
+ primaryImageResource = R.drawable.mozac_ic_arrow_clockwise_24,
+ primaryContentDescription = context.getString(R.string.content_description_reload),
+ primaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ isInPrimaryState = {
+ selectedSession?.content?.loading == false
+ },
+ secondaryImageResource = R.drawable.mozac_ic_stop,
+ secondaryContentDescription = context.getString(R.string.content_description_stop),
+ secondaryImageTintResource = context.theme.resolveAttribute(R.attr.primaryText),
+ disableInSecondaryState = false,
+ longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Reload) },
+ ) {
+ if (selectedSession?.content?.loading == true) {
+ onItemTapped.invoke(ToolbarMenu.Item.Stop)
+ } else {
+ onItemTapped.invoke(ToolbarMenu.Item.Reload)
+ }
+ }
+ val share = BrowserMenuItemToolbar.Button(
+ imageResource = R.drawable.mozac_ic_share_android_24,
+ contentDescription = context.getString(R.string.menu_share),
+ iconTintColorResource = context.theme.resolveAttribute(R.attr.primaryText),
+ listener = {
+ onItemTapped.invoke(ToolbarMenu.Item.Share)
+ },
+ )
+ BrowserMenuItemToolbar(listOf(back, forward, share, refresh))
+ }
+
+ private val mvpMenuItems by lazy {
+
+ val shortcuts = TwoStateBrowserMenuImageText(
+ primaryLabel = context.getString(R.string.menu_add_to_shortcuts),
+ primaryStateIconResource = R.drawable.mozac_ic_pin_24,
+ secondaryLabel = context.getString(R.string.menu_remove_from_shortcuts),
+ secondaryStateIconResource = R.drawable.mozac_ic_pin_slash_24,
+ isInPrimaryState = {
+ appStore.state.topSites.find { it.url == selectedSession?.content?.url } == null &&
+ selectedSession?.content?.url != null && appStore.state.topSites.size < TOP_SITES_MAX_LIMIT
+ },
+ isInSecondaryState = {
+ appStore.state.topSites.find { it.url == selectedSession?.content?.url } != null
+ },
+ primaryStateAction = { onItemTapped.invoke(ToolbarMenu.Item.AddToShortcuts) },
+ secondaryStateAction = { onItemTapped.invoke(ToolbarMenu.Item.RemoveFromShortcuts) },
+ )
+
+ val shortcutsDivider = BrowserMenuDivider().apply {
+ visible = shortcuts.visible
+ }
+
+ val findInPage = BrowserMenuImageText(
+ label = context.getString(R.string.find_in_page),
+ imageResource = R.drawable.mozac_ic_search_24,
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.FindInPage)
+ }
+
+ val desktopMode = BrowserMenuImageSwitch(
+ imageResource = R.drawable.mozac_ic_device_desktop_24,
+ label = context.getString(R.string.preference_performance_request_desktop_site2),
+ initialState = {
+ selectedSession?.content?.desktopMode ?: false
+ },
+ ) { checked ->
+ onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked))
+ }
+
+ val reportSiteIssuePlaceholder = WebExtensionPlaceholderMenuItem(
+ id = WebCompatReporterFeature.WEBCOMPAT_REPORTER_EXTENSION_ID,
+ iconTintColorResource = context.theme.resolveAttribute(R.attr.primaryText),
+ )
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun canAddToHomescreen(): Boolean =
+ selectedSession != null && isPinningSupported
+
+ val addToHomescreen = BrowserMenuImageText(
+ label = context.getString(R.string.menu_add_to_home_screen),
+ imageResource = R.drawable.mozac_ic_add_to_homescreen_24,
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen)
+ }
+
+ val openInApp = BrowserMenuImageText(
+ label = context.getString(R.string.menu_open_with_a_browser2),
+ imageResource = R.drawable.mozac_ic_open_in,
+ textColorResource = context.theme.resolveAttribute(R.attr.primaryText),
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.OpenInApp)
+ }
+
+ val settings = BrowserMenuImageText(
+ label = context.getString(R.string.menu_settings),
+ imageResource = R.drawable.mozac_ic_settings_24,
+ textColorResource = context.theme.resolveAttribute(R.attr.primaryText),
+ ) {
+ onItemTapped.invoke(ToolbarMenu.Item.Settings)
+ }
+
+ listOfNotNull(
+ menuToolbar,
+ BrowserMenuDivider(),
+ shortcuts,
+ shortcutsDivider,
+ findInPage,
+ desktopMode,
+ reportSiteIssuePlaceholder,
+ BrowserMenuDivider(),
+ addToHomescreen.apply { visible = ::canAddToHomescreen },
+ openInApp,
+ BrowserMenuDivider(),
+ settings,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenu.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenu.kt
new file mode 100644
index 0000000000..dd343352b0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenu.kt
@@ -0,0 +1,35 @@
+/* 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.focus.menu.home
+
+import android.content.Context
+import mozilla.components.browser.menu.BrowserMenuBuilder
+import mozilla.components.browser.menu.item.BrowserMenuImageText
+import org.mozilla.focus.R
+
+/**
+ * The overflow menu shown on the start/home screen.
+ */
+class HomeMenu(
+ private val context: Context,
+ private val onItemTapped: ((HomeMenuItem) -> Unit),
+) {
+ fun getMenuBuilder(): BrowserMenuBuilder {
+ val help = BrowserMenuImageText(
+ label = context.getString(R.string.menu_help),
+ imageResource = R.drawable.mozac_ic_help_circle_24,
+ ) {
+ onItemTapped.invoke(HomeMenuItem.Help)
+ }
+
+ val settings = BrowserMenuImageText(
+ label = context.getString(R.string.menu_settings),
+ imageResource = R.drawable.mozac_ic_settings_24,
+ ) {
+ onItemTapped.invoke(HomeMenuItem.Settings)
+ }
+ return BrowserMenuBuilder(items = listOf(help, settings))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenuItem.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenuItem.kt
new file mode 100644
index 0000000000..7ab78d1770
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/menu/home/HomeMenuItem.kt
@@ -0,0 +1,10 @@
+/* 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.focus.menu.home
+
+sealed class HomeMenuItem {
+ object Help : HomeMenuItem()
+ object Settings : HomeMenuItem()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/MainActivityNavigation.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/MainActivityNavigation.kt
new file mode 100644
index 0000000000..b2f50932a1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/MainActivityNavigation.kt
@@ -0,0 +1,315 @@
+/* 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.focus.navigation
+
+import android.os.Build
+import android.os.Bundle
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.autocomplete.AutocompleteAddFragment
+import org.mozilla.focus.autocomplete.AutocompleteListFragment
+import org.mozilla.focus.autocomplete.AutocompleteRemoveFragment
+import org.mozilla.focus.autocomplete.AutocompleteSettingsFragment
+import org.mozilla.focus.biometrics.BiometricAuthenticationFragment
+import org.mozilla.focus.cookiebanner.CookieBannerFragment
+import org.mozilla.focus.exceptions.ExceptionsListFragment
+import org.mozilla.focus.exceptions.ExceptionsRemoveFragment
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.fragment.BrowserFragment
+import org.mozilla.focus.fragment.FirstrunFragment
+import org.mozilla.focus.fragment.UrlInputFragment
+import org.mozilla.focus.fragment.about.AboutFragment
+import org.mozilla.focus.fragment.onboarding.OnboardingFirstFragment
+import org.mozilla.focus.fragment.onboarding.OnboardingSecondFragment
+import org.mozilla.focus.fragment.onboarding.OnboardingStep
+import org.mozilla.focus.fragment.onboarding.OnboardingStorage
+import org.mozilla.focus.locale.screen.LanguageFragment
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.nimbus.Onboarding
+import org.mozilla.focus.searchwidget.SearchWidgetUtils
+import org.mozilla.focus.settings.AboutLibrariesFragment
+import org.mozilla.focus.settings.GeneralSettingsFragment
+import org.mozilla.focus.settings.InstalledSearchEnginesSettingsFragment
+import org.mozilla.focus.settings.ManualAddSearchEngineSettingsFragment
+import org.mozilla.focus.settings.MozillaSettingsFragment
+import org.mozilla.focus.settings.RemoveSearchEnginesSettingsFragment
+import org.mozilla.focus.settings.SearchSettingsFragment
+import org.mozilla.focus.settings.SettingsFragment
+import org.mozilla.focus.settings.advanced.AdvancedSettingsFragment
+import org.mozilla.focus.settings.advanced.SecretSettingsFragment
+import org.mozilla.focus.settings.permissions.SitePermissionsFragment
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsFragment
+import org.mozilla.focus.settings.privacy.PrivacySecuritySettingsFragment
+import org.mozilla.focus.settings.privacy.studies.StudiesFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.utils.ViewUtils
+import kotlin.collections.forEach as withEach
+
+/**
+ * Class performing the actual navigation in [MainActivity] by performing fragment transactions if
+ * needed.
+ */
+@Suppress("TooManyFunctions")
+class MainActivityNavigation(
+ private val activity: MainActivity,
+) {
+ /**
+ * Home screen.
+ */
+ fun home() {
+ val fragmentManager = activity.supportFragmentManager
+ val browserFragment = fragmentManager.findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
+
+ val isShowingBrowser = browserFragment != null
+ val crashReporterIsVisible = browserFragment?.crashReporterIsVisible() ?: false
+
+ if (isShowingBrowser && !crashReporterIsVisible) {
+ showPromoteSearchWidgetDialogOrBrandedSnackbar()
+ }
+
+ // We add the url input fragment to the layout if it doesn't exist yet.
+ val transaction = fragmentManager
+ .beginTransaction()
+
+ // We only want to play the animation if a browser fragment is added and resumed.
+ // If it is not resumed then the application is currently in the process of resuming
+ // and the session was removed while the app was in the background (e.g. via the
+ // notification). In this case we do not want to show the content and remove the
+ // browser fragment immediately.
+ val shouldAnimate = isShowingBrowser && browserFragment!!.isResumed
+
+ if (shouldAnimate) {
+ transaction.setCustomAnimations(0, R.anim.erase_animation)
+ }
+
+ showStartBrowsingCfr()
+ // Currently this callback can get invoked while the app is in the background. Therefore we are using
+ // commitAllowingStateLoss() here because we can't do a fragment transaction while the app is in the
+ // background - like we already do in showBrowserScreenForCurrentSession().
+ // Ideally we'd make it possible to pause observers while the app is in the background:
+ // https://github.com/mozilla-mobile/android-components/issues/876
+ transaction
+ .replace(
+ R.id.container,
+ UrlInputFragment.createWithoutSession(),
+ UrlInputFragment.FRAGMENT_TAG,
+ )
+ .commitAllowingStateLoss()
+ }
+
+ private fun showStartBrowsingCfr() {
+ val onboardingConfig = FocusNimbus.features.onboarding.value()
+ if (
+ onboardingConfig.isCfrEnabled &&
+ !activity.settings.isFirstRun &&
+ activity.settings.shouldShowStartBrowsingCfr
+ ) {
+ FocusNimbus.features.onboarding.recordExposure()
+ activity.components.appStore.dispatch(AppAction.ShowStartBrowsingCfrChange(true))
+ }
+ }
+
+ /**
+ * Display the widget promo at first data clearing action and if it wasn't added after 5th Focus session
+ * or display branded snackbar when widget promo is not shown.
+ */
+ @Suppress("MagicNumber")
+ private fun showPromoteSearchWidgetDialogOrBrandedSnackbar() {
+ val onboardingFeature = FocusNimbus.features.onboarding
+ val onboardingConfig = onboardingFeature.value()
+
+ val clearBrowsingSessions = activity.components.settings.getClearBrowsingSessions()
+ activity.components.settings.addClearBrowsingSessions(1)
+
+ if (shouldShowPromoteSearchWidgetDialog(onboardingConfig) &&
+ (
+ clearBrowsingSessions == 0 || clearBrowsingSessions == 4
+ )
+ ) {
+ onboardingFeature.recordExposure()
+ SearchWidgetUtils.showPromoteSearchWidgetDialog(activity)
+ } else {
+ ViewUtils.showBrandedSnackbar(
+ activity.findViewById(android.R.id.content),
+ R.string.feedback_erase2,
+ activity.resources.getInteger(R.integer.erase_snackbar_delay),
+ )
+ }
+ }
+
+ private fun shouldShowPromoteSearchWidgetDialog(onboadingConfig: Onboarding): Boolean {
+ return (
+ onboadingConfig.isPromoteSearchWidgetDialogEnabled &&
+ !activity.components.settings.searchWidgetInstalled
+ )
+ }
+
+ /**
+ * Show browser for tab with the given [tabId].
+ */
+ fun browser(tabId: String) {
+ val fragmentManager = activity.supportFragmentManager
+
+ val urlInputFragment = fragmentManager.findFragmentByTag(UrlInputFragment.FRAGMENT_TAG) as UrlInputFragment?
+ if (urlInputFragment != null) {
+ fragmentManager
+ .beginTransaction()
+ .remove(urlInputFragment)
+ .commitAllowingStateLoss()
+ }
+
+ val browserFragment = fragmentManager.findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
+ if (browserFragment == null || browserFragment.tab.id != tabId) {
+ fragmentManager
+ .beginTransaction()
+ .replace(R.id.container, BrowserFragment.createForTab(tabId), BrowserFragment.FRAGMENT_TAG)
+ .commitAllowingStateLoss()
+ }
+ }
+
+ /**
+ * Edit URL of tab with the given [tabId].
+ */
+ fun edit(
+ tabId: String,
+ ) {
+ val fragmentManager = activity.supportFragmentManager
+
+ val urlInputFragment = fragmentManager.findFragmentByTag(UrlInputFragment.FRAGMENT_TAG) as UrlInputFragment?
+ if (urlInputFragment != null && urlInputFragment.tab?.id == tabId) {
+ // There's already an UrlInputFragment for this tab.
+ return
+ }
+
+ val urlFragment = UrlInputFragment.createWithTab(tabId)
+
+ fragmentManager
+ .beginTransaction()
+ .add(R.id.container, urlFragment, UrlInputFragment.FRAGMENT_TAG)
+ .commit()
+ }
+
+ /**
+ * Show first run onBoarding.
+ */
+ fun firstRun() {
+ val onboardingFragment = if (activity.settings.isNewOnboardingEnable) {
+ FocusNimbus.features.onboarding.recordExposure()
+ val onBoardingStorage = OnboardingStorage(activity)
+ when (onBoardingStorage.getCurrentOnboardingStep()) {
+ OnboardingStep.ON_BOARDING_FIRST_SCREEN -> {
+ OnboardingFirstFragment()
+ }
+ OnboardingStep.ON_BOARDING_SECOND_SCREEN -> {
+ OnboardingSecondFragment()
+ }
+ }
+ } else {
+ FirstrunFragment.create()
+ }
+
+ activity.supportFragmentManager
+ .beginTransaction()
+ .replace(R.id.container, onboardingFragment, onboardingFragment::class.java.simpleName)
+ .commit()
+ }
+
+ fun showOnBoardingSecondScreen() {
+ activity.supportFragmentManager
+ .beginTransaction()
+ .replace(R.id.container, OnboardingSecondFragment(), OnboardingSecondFragment::class.java.simpleName)
+ .commit()
+ }
+
+ /**
+ * Lock app.
+ *
+ * @param bundle it is used for app navigation. If the user can unlock with success he should
+ * be redirected to a certain screen. It comes from the external intent.
+ */
+ fun lock(bundle: Bundle? = null) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ throw IllegalStateException("Trying to lock unsupported device")
+ }
+
+ val fragmentManager = activity.supportFragmentManager
+ if (fragmentManager.findFragmentByTag(BiometricAuthenticationFragment.FRAGMENT_TAG) != null) {
+ return
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInPictureInPictureMode) {
+ return
+ }
+
+ val transaction = fragmentManager
+ .beginTransaction()
+
+ fragmentManager.fragments.withEach { fragment ->
+ transaction.remove(fragment)
+ }
+
+ fragmentManager
+ .beginTransaction()
+ .replace(
+ R.id.container,
+ BiometricAuthenticationFragment.createWithDestinationData(bundle),
+ BiometricAuthenticationFragment.FRAGMENT_TAG,
+ )
+ .commit()
+ }
+
+ @Suppress("ComplexMethod")
+ fun settings(page: Screen.Settings.Page) {
+ val fragment = when (page) {
+ Screen.Settings.Page.Start -> SettingsFragment()
+ Screen.Settings.Page.General -> GeneralSettingsFragment()
+ Screen.Settings.Page.Privacy -> PrivacySecuritySettingsFragment()
+ Screen.Settings.Page.Search -> SearchSettingsFragment()
+ Screen.Settings.Page.Advanced -> AdvancedSettingsFragment()
+ Screen.Settings.Page.Mozilla -> MozillaSettingsFragment()
+ Screen.Settings.Page.PrivacyExceptions -> ExceptionsListFragment()
+ Screen.Settings.Page.PrivacyExceptionsRemove -> ExceptionsRemoveFragment()
+ Screen.Settings.Page.SitePermissions -> SitePermissionsFragment()
+ Screen.Settings.Page.Studies -> StudiesFragment()
+ Screen.Settings.Page.SecretSettings -> SecretSettingsFragment()
+ Screen.Settings.Page.SearchList -> InstalledSearchEnginesSettingsFragment()
+ Screen.Settings.Page.SearchRemove -> RemoveSearchEnginesSettingsFragment()
+ Screen.Settings.Page.SearchAdd -> ManualAddSearchEngineSettingsFragment()
+ Screen.Settings.Page.SearchAutocomplete -> AutocompleteSettingsFragment()
+ Screen.Settings.Page.SearchAutocompleteList -> AutocompleteListFragment()
+ Screen.Settings.Page.SearchAutocompleteAdd -> AutocompleteAddFragment()
+ Screen.Settings.Page.SearchAutocompleteRemove -> AutocompleteRemoveFragment()
+ Screen.Settings.Page.About -> AboutFragment()
+ Screen.Settings.Page.Licenses -> AboutLibrariesFragment()
+ Screen.Settings.Page.Locale -> LanguageFragment()
+ Screen.Settings.Page.CookieBanner -> CookieBannerFragment()
+ }
+
+ val tag = "settings_" + fragment::class.java.simpleName
+
+ val fragmentManager = activity.supportFragmentManager
+ if (fragmentManager.findFragmentByTag(tag) != null) {
+ return
+ }
+
+ fragmentManager.beginTransaction()
+ .replace(R.id.container, fragment, tag)
+ .commit()
+ }
+
+ fun sitePermissionOptionsFragment(sitePermission: SitePermission) {
+ val fragmentManager = activity.supportFragmentManager
+ fragmentManager.beginTransaction()
+ .replace(
+ R.id.container,
+ SitePermissionOptionsFragment.addSitePermission(sitePermission = sitePermission),
+ SitePermissionOptionsFragment.FRAGMENT_TAG,
+ )
+ .commit()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/Navigator.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/Navigator.kt
new file mode 100644
index 0000000000..146ec6e88c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/Navigator.kt
@@ -0,0 +1,56 @@
+/* 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.focus.navigation
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.map
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+import org.mozilla.focus.state.AppState
+import org.mozilla.focus.state.AppStore
+import org.mozilla.focus.state.Screen
+
+/**
+ * The [Navigator] subscribes to the [AppStore] and initiates a navigation with the help of the
+ * provided [MainActivityNavigation] if the [Screen] in the [AppState] changes.
+ */
+class Navigator(
+ val store: AppStore,
+ val navigation: MainActivityNavigation,
+) : LifecycleAwareFeature {
+ private var scope: CoroutineScope? = null
+
+ override fun start() {
+ scope = store.flowScoped { flow -> subscribe(flow) }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+
+ private suspend fun subscribe(flow: Flow) {
+ flow.map { state -> state.screen }
+ .distinctUntilChangedBy { screen -> screen.id }
+ .collect { screen -> navigateTo(screen) }
+ }
+
+ private fun navigateTo(screen: Screen) {
+ when (screen) {
+ is Screen.Home -> navigation.home()
+ is Screen.Browser -> navigation.browser(screen.tabId)
+ is Screen.EditUrl -> navigation.edit(
+ screen.tabId,
+ )
+ is Screen.FirstRun -> navigation.firstRun()
+ is Screen.Locked -> navigation.lock(screen.bundle)
+ is Screen.Settings -> navigation.settings(screen.page)
+ is Screen.SitePermissionOptionsScreen -> navigation.sitePermissionOptionsFragment(screen.sitePermission)
+ is Screen.OnboardingSecondScreen -> navigation.showOnBoardingSecondScreen()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/StoreLink.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/StoreLink.kt
new file mode 100644
index 0000000000..db93070c6e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/navigation/StoreLink.kt
@@ -0,0 +1,50 @@
+/* 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.focus.navigation
+
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.lib.state.ext.flow
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+
+/**
+ * Helper for subscribing to the [BrowserStore] and updating the [AppStore] for certain state changes.
+ */
+class StoreLink(
+ private val appStore: AppStore,
+ private val browserStore: BrowserStore,
+) {
+ fun start() {
+ MainScope().also { scope ->
+ scope.launch { observeSelectionChanges(browserStore.flow()) }
+ scope.launch { observeTabsClosed(browserStore.flow()) }
+ }
+ }
+
+ private suspend fun observeSelectionChanges(flow: Flow) {
+ flow.map { state -> state.selectedTabId }
+ .distinctUntilChanged()
+ .filterNotNull()
+ .collect { tabId -> appStore.dispatch(AppAction.SelectionChanged(tabId)) }
+ }
+
+ private suspend fun observeTabsClosed(flow: Flow) {
+ flow.map { state -> state.privateTabs.isEmpty() }
+ .distinctUntilChanged()
+ .filter { isEmpty -> isEmpty }
+ .collect {
+ appStore.dispatch(AppAction.NoTabs)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppAdapter.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppAdapter.kt
new file mode 100644
index 0000000000..670982cac6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppAdapter.kt
@@ -0,0 +1,114 @@
+/* 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.focus.open
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.graphics.drawable.Drawable
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * An adapter for displaying app items in the [OpenWithFragment] dialog.
+ * This will display browser apps and an item that can be used for installing Firefox,
+ * if it is not already installed on the device.
+ *
+ * @param infoArray List of browser apps.
+ * @param store Store app for installing Firefox.
+ */
+class AppAdapter(context: Context, infoArray: Array, store: ActivityInfo?) :
+ RecyclerView.Adapter() {
+ /**
+ * Class for an app installed on the device.
+ */
+ class App(private val context: Context, private val info: ActivityInfo) {
+ val label: String = info.loadLabel(context.packageManager).toString()
+
+ /**
+ * Retrieve the current graphical icon associated with the app.
+ */
+ fun loadIcon(): Drawable {
+ return info.loadIcon(context.packageManager)
+ }
+
+ val packageName: String
+ get() = info.packageName
+ val activityName: String
+ get() = info.name
+ }
+
+ /**
+ * Interface for setting a listener for an [App] item.
+ */
+ interface OnAppSelectedListener {
+ /**
+ * Action to be performed on an item from [AppAdapter] list being clicked.
+ */
+ fun onAppSelected(app: App)
+ }
+
+ private val apps: List
+ private val store: App?
+ private var listener: OnAppSelectedListener? = null
+
+ init {
+ this.apps = infoArray.map { App(context, it) }
+ .sortedWith { app1, app2 -> app1.label.compareTo(app2.label) }
+ this.store = if (store != null) App(context, store) else null
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ if (viewType == AppViewHolder.LAYOUT_ID) {
+ return AppViewHolder(
+ inflater.inflate(AppViewHolder.LAYOUT_ID, parent, false),
+ )
+ } else if (viewType == InstallBannerViewHolder.LAYOUT_ID) {
+ return InstallBannerViewHolder(
+ inflater.inflate(InstallBannerViewHolder.LAYOUT_ID, parent, false),
+ )
+ }
+ throw IllegalStateException("Unknown view type: $viewType")
+ }
+
+ /**
+ * Sets the listener for the [AppAdapter].
+ */
+ fun setOnAppSelectedListener(listener: OnAppSelectedListener?) {
+ this.listener = listener
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (position < apps.size) {
+ AppViewHolder.LAYOUT_ID
+ } else {
+ InstallBannerViewHolder.LAYOUT_ID
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val itemViewType = holder.itemViewType
+ if (itemViewType == AppViewHolder.LAYOUT_ID) {
+ bindApp(holder, position)
+ } else if (itemViewType == InstallBannerViewHolder.LAYOUT_ID) {
+ bindInstallBanner(holder)
+ }
+ }
+
+ private fun bindApp(holder: RecyclerView.ViewHolder, position: Int) {
+ val appViewHolder = holder as AppViewHolder
+ val app = apps[position]
+ appViewHolder.bind(app, listener)
+ }
+
+ private fun bindInstallBanner(holder: RecyclerView.ViewHolder) {
+ val installViewHolder = holder as InstallBannerViewHolder
+ store?.let { installViewHolder.bind(it) }
+ }
+
+ override fun getItemCount(): Int {
+ return apps.size + if (store != null) 1 else 0
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppViewHolder.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppViewHolder.kt
new file mode 100644
index 0000000000..23d6d3d70f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/AppViewHolder.kt
@@ -0,0 +1,45 @@
+/* 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.focus.open
+
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import org.mozilla.focus.R
+import org.mozilla.focus.open.AppAdapter.OnAppSelectedListener
+
+/**
+ * View holder for an app item in the [OpenWithFragment] list.
+ */
+class AppViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val titleView: TextView
+ private val iconView: ImageView
+
+ /* package */
+ init {
+ titleView = itemView.findViewById(R.id.title)
+ iconView = itemView.findViewById(R.id.icon)
+ }
+
+ /**
+ * Binds the [AppViewHolder] item.
+ */
+ fun bind(app: AppAdapter.App, listener: OnAppSelectedListener?) {
+ titleView.text = app.label
+ iconView.setImageDrawable(app.loadIcon())
+ itemView.setOnClickListener(createListenerWrapper(app, listener))
+ }
+
+ private fun createListenerWrapper(
+ app: AppAdapter.App,
+ listener: OnAppSelectedListener?,
+ ): View.OnClickListener {
+ return View.OnClickListener { listener?.onAppSelected(app) }
+ }
+
+ companion object {
+ const val LAYOUT_ID = R.layout.item_app
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/InstallBannerViewHolder.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/InstallBannerViewHolder.kt
new file mode 100644
index 0000000000..2acf7ff35d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/InstallBannerViewHolder.kt
@@ -0,0 +1,39 @@
+/* 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.focus.open
+
+import android.view.View
+import android.widget.ImageView
+import androidx.recyclerview.widget.RecyclerView
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.InstallFirefoxActivity.Companion.open
+
+/**
+ * View holder for install Firefox item in the [OpenWithFragment] list.
+ */
+class InstallBannerViewHolder(
+ itemView: View,
+) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
+ private val iconView: ImageView
+
+ init {
+ iconView = itemView.findViewById(R.id.icon)
+ itemView.setOnClickListener(this)
+ }
+
+ /**
+ * Binds the [InstallBannerViewHolder] item.
+ */
+ fun bind(store: AppAdapter.App) {
+ iconView.setImageDrawable(store.loadIcon())
+ }
+
+ override fun onClick(view: View) {
+ open(view.context)
+ }
+
+ companion object {
+ const val LAYOUT_ID = R.layout.item_install_banner
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/OpenWithFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/OpenWithFragment.kt
new file mode 100644
index 0000000000..91789fa17d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/open/OpenWithFragment.kt
@@ -0,0 +1,145 @@
+/* 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.focus.open
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.net.Uri
+import android.os.Bundle
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AppCompatDialogFragment
+import androidx.fragment.app.commit
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import mozilla.components.support.utils.ext.getParcelableArrayCompat
+import mozilla.components.support.utils.ext.getParcelableCompat
+import org.mozilla.focus.GleanMetrics.OpenWith.ListItemTappedExtra
+import org.mozilla.focus.GleanMetrics.OpenWith.listItemTapped
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.isTablet
+import org.mozilla.focus.open.AppAdapter.OnAppSelectedListener
+
+/**
+ * [AppCompatDialogFragment] used to display open in app options.
+ */
+class OpenWithFragment : AppCompatDialogFragment(), OnAppSelectedListener {
+ override fun onPause() {
+ requireActivity().supportFragmentManager.commit(allowStateLoss = true) {
+ remove(this@OpenWithFragment)
+ }
+ super.onPause()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val wrapper = ContextThemeWrapper(context, android.R.style.Theme_Material_Light)
+
+ @SuppressLint("InflateParams") // This View will have it's params ignored anyway:
+ val view = LayoutInflater.from(wrapper).inflate(R.layout.fragment_open_with, null)
+ val dialog: Dialog = CustomWidthBottomSheetDialog(wrapper)
+ dialog.setContentView(view)
+ val appList = view.findViewById(R.id.apps)
+ appList.layoutManager = LinearLayoutManager(
+ wrapper,
+ RecyclerView.VERTICAL,
+ false,
+ )
+ val adapter = requireArguments().getParcelableArrayCompat(ARGUMENT_KEY_APPS, ActivityInfo::class.java)
+ ?.let { infoArray ->
+ AppAdapter(
+ wrapper,
+ infoArray,
+ requireArguments().getParcelableCompat(ARGUMENT_STORE, ActivityInfo::class.java),
+ )
+ }
+
+ adapter?.setOnAppSelectedListener(this)
+ appList.adapter = adapter
+ return dialog
+ }
+
+ internal class CustomWidthBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
+ private var contentView: View? = null
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // The support library makes the bottomsheet full width on all devices (and then uses a 16:9
+ // keyline). On tablets, the system bottom sheets use a narrower width - lets do that too:
+ if (context.isTablet()) {
+ val width =
+ context.resources.getDimensionPixelSize(R.dimen.tablet_bottom_sheet_width)
+ val window = window
+ window?.setLayout(width, ViewGroup.LayoutParams.MATCH_PARENT)
+ }
+ }
+
+ override fun setContentView(view: View) {
+ super.setContentView(view)
+ contentView = view
+ }
+
+ override fun setContentView(@LayoutRes layoutResID: Int) {
+ throw IllegalStateException("CustomWidthBottomSheetDialog only supports setContentView(View)")
+ }
+
+ override fun setContentView(view: View, params: ViewGroup.LayoutParams?) {
+ throw IllegalStateException("CustomWidthBottomSheetDialog only supports setContentView(View)")
+ }
+
+ override fun show() {
+ if (context.isTablet()) {
+ val peekHeight =
+ context.resources.getDimensionPixelSize(R.dimen.tablet_bottom_sheet_peekheight)
+ val bsBehaviour = BottomSheetBehavior.from(
+ contentView!!.parent as View,
+ )
+ bsBehaviour.peekHeight = peekHeight
+ }
+ super.show()
+ }
+ }
+
+ override fun onAppSelected(app: AppAdapter.App) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(requireArguments().getString(ARGUMENT_URL)))
+ intent.setClassName(app.packageName, app.activityName)
+ startActivity(intent)
+
+ listItemTapped.record(ListItemTappedExtra(app.packageName.contains("mozilla")))
+
+ dismiss()
+ }
+
+ companion object {
+ const val FRAGMENT_TAG = "open_with"
+ private const val ARGUMENT_KEY_APPS = "apps"
+ private const val ARGUMENT_URL = "url"
+ private const val ARGUMENT_STORE = "store"
+
+ /**
+ * Creates a new instance of [AppCompatDialogFragment].
+ */
+ fun newInstance(
+ apps: Array?,
+ url: String?,
+ store: ActivityInfo?,
+ ): OpenWithFragment {
+ val arguments = Bundle()
+ arguments.putParcelableArray(ARGUMENT_KEY_APPS, apps)
+ arguments.putString(ARGUMENT_URL, url)
+ arguments.putParcelable(ARGUMENT_STORE, store)
+ val fragment = OpenWithFragment()
+ fragment.arguments = arguments
+ return fragment
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/perf/Performance.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/perf/Performance.kt
new file mode 100644
index 0000000000..da1d8a2500
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/perf/Performance.kt
@@ -0,0 +1,52 @@
+/* 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.focus.perf
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.BatteryManager
+import android.os.Bundle
+import android.provider.Settings as AndroidSettings
+
+/**
+ * A collection of objects related to app performance.
+ */
+object Performance {
+
+ private const val EXTRA_IS_PERFORMANCE_TEST = "performancetest"
+
+ fun processIntentIfPerformanceTest(bundle: Bundle?, context: Context) = isPerformanceTest(bundle, context)
+
+ /**
+ * This checks for the charging state and ADB debugging in case another application tries to
+ * leverage this intent to trigger a code path for Firefox that shouldn't be used unless
+ * it is for testing visual metrics. These checks aren't foolproof but most of our users won't
+ * have ADB on and charging at the same time when running Firefox.
+ */
+ private fun isPerformanceTest(bundle: Bundle?, context: Context): Boolean {
+ if (bundle == null || !bundle.getBoolean(EXTRA_IS_PERFORMANCE_TEST, false)) {
+ return false
+ }
+
+ val batteryStatus = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+ batteryStatus?.let {
+ // We only run perf tests when the device is connected to USB. However, AC may be reported
+ // instead if the device is connected through a USB hub so we check both states.
+ val extraPlugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
+ val isPhonePlugged = extraPlugged == BatteryManager.BATTERY_PLUGGED_USB ||
+ extraPlugged == BatteryManager.BATTERY_PLUGGED_AC
+
+ val isAdbEnabled = AndroidSettings.Global.getInt(
+ context.contentResolver,
+ AndroidSettings.Global.ADB_ENABLED,
+ 0,
+ ) == 1
+
+ return isPhonePlugged && isAdbEnabled
+ }
+ return false
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/ManualAddSearchEnginePreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/ManualAddSearchEnginePreference.kt
new file mode 100644
index 0000000000..77ee37eaf4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/ManualAddSearchEnginePreference.kt
@@ -0,0 +1,126 @@
+/* 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.focus.search
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Parcelable
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.widget.EditText
+import android.widget.ProgressBar
+import androidx.core.os.bundleOf
+import androidx.core.view.isVisible
+import androidx.core.widget.doOnTextChanged
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.google.android.material.textfield.TextInputLayout
+import mozilla.components.browser.state.search.SearchEngine
+import mozilla.components.support.ktx.util.URLStringUtils
+import mozilla.components.support.utils.ext.getParcelableCompat
+import org.mozilla.focus.R
+import org.mozilla.focus.utils.ViewUtils
+
+class ManualAddSearchEnginePreference(context: Context, attrs: AttributeSet) :
+ Preference(context, attrs) {
+ private var engineNameEditText: EditText? = null
+ private var searchQueryEditText: EditText? = null
+ private var engineNameErrorLayout: TextInputLayout? = null
+ private var searchQueryErrorLayout: TextInputLayout? = null
+
+ private var progressView: ProgressBar? = null
+
+ private var savedSearchEngineName: String? = null
+ private var savedSearchQuery: String? = null
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ engineNameErrorLayout =
+ holder.findViewById(R.id.edit_engine_name_layout) as TextInputLayout
+ searchQueryErrorLayout =
+ holder.findViewById(R.id.edit_search_string_layout) as TextInputLayout
+
+ engineNameEditText = holder.findViewById(R.id.edit_engine_name) as EditText
+
+ engineNameEditText?.doOnTextChanged { _, _, _, _ ->
+ engineNameErrorLayout?.error = null
+ }
+
+ searchQueryEditText = holder.findViewById(R.id.edit_search_string) as EditText
+
+ searchQueryEditText?.doOnTextChanged { _, _, _, _ ->
+ searchQueryErrorLayout?.error = null
+ }
+
+ progressView = holder.findViewById(R.id.progress) as ProgressBar
+
+ updateState()
+
+ ViewUtils.showKeyboard(engineNameEditText)
+ }
+
+ override fun onRestoreInstanceState(state: Parcelable?) {
+ val bundle = state as Bundle
+ super.onRestoreInstanceState(bundle.getParcelableCompat(SUPER_STATE_KEY, Parcelable::class.java))
+ savedSearchEngineName = bundle.getString(SEARCH_ENGINE_NAME_KEY)
+ savedSearchQuery = bundle.getString(SEARCH_QUERY_KEY)
+ }
+
+ override fun onSaveInstanceState(): Parcelable {
+ val state = super.onSaveInstanceState()
+
+ return bundleOf(
+ SUPER_STATE_KEY to state,
+ SEARCH_ENGINE_NAME_KEY to engineNameEditText?.text.toString(),
+ SEARCH_QUERY_KEY to searchQueryEditText?.text.toString(),
+ )
+ }
+
+ fun validateEngineNameAndShowError(engineName: String, existingEngines: List): Boolean {
+ val errorMessage = when {
+ TextUtils.isEmpty(engineName) ->
+ context.getString(R.string.search_add_error_empty_name)
+
+ existingEngines.any { it.name.equals(engineName, ignoreCase = true) } ->
+ context.getString(R.string.search_add_error_duplicate_name)
+
+ else -> null
+ }
+
+ engineNameErrorLayout?.error = errorMessage
+ return errorMessage == null
+ }
+
+ fun validateSearchQueryAndShowError(searchQuery: String): Boolean {
+ val errorMessage = when {
+ TextUtils.isEmpty(searchQuery) -> context.getString(R.string.search_add_error_empty_search)
+ !URLStringUtils.isValidSearchQueryUrl(searchQuery) -> context.getString(R.string.search_add_error_format)
+ else -> null
+ }
+
+ searchQueryErrorLayout?.error = errorMessage
+ return errorMessage == null
+ }
+
+ fun setSearchQueryErrorText(err: String) {
+ searchQueryErrorLayout?.error = err
+ }
+
+ fun setProgressViewShown(isShown: Boolean) {
+ progressView?.isVisible = isShown
+ }
+
+ private fun updateState() {
+ if (engineNameEditText != null) engineNameEditText?.setText(savedSearchEngineName)
+ if (searchQueryEditText != null) searchQueryEditText?.setText(savedSearchQuery)
+ }
+
+ companion object {
+ private val SUPER_STATE_KEY = "super-state"
+ private val SEARCH_ENGINE_NAME_KEY = "search-engine-name"
+ private val SEARCH_QUERY_KEY = "search-query"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/MultiselectSearchEngineListPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/MultiselectSearchEngineListPreference.kt
new file mode 100644
index 0000000000..671d3c9463
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/MultiselectSearchEngineListPreference.kt
@@ -0,0 +1,66 @@
+/* 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.focus.search
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.CompoundButton
+import androidx.preference.PreferenceViewHolder
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.tryAsActivity
+
+class MultiselectSearchEngineListPreference(context: Context, attrs: AttributeSet) :
+ SearchEngineListPreference(context, attrs) {
+
+ override val itemResId: Int
+ get() = R.layout.search_engine_checkbox_button
+
+ val checkedEngineIds: Set
+ get() {
+ val engineIdSet = HashSet()
+
+ for (i in 0 until searchEngineGroup!!.childCount) {
+ val engineButton = searchEngineGroup!!.getChildAt(i) as CompoundButton
+ if (engineButton.isChecked) {
+ engineIdSet.add(engineButton.tag as String)
+ }
+ }
+ return engineIdSet
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ bindEngineCheckboxesToMenu()
+ }
+
+ override fun updateDefaultItem(defaultButton: CompoundButton) {
+ defaultButton.isClickable = false
+ // Showing the default engine as disabled requires a StateListDrawable, but since there
+ // is no state_clickable and state_enabled seems to require a default drawable state,
+ // use state_activated instead to designate the default search engine.
+ defaultButton.isActivated = true
+ }
+
+ // Whenever an engine is checked or unchecked, we notify the menu
+ private fun bindEngineCheckboxesToMenu() {
+ for (i in 0 until searchEngineGroup!!.childCount) {
+ val engineButton = searchEngineGroup!!.getChildAt(i) as CompoundButton
+ engineButton.setOnCheckedChangeListener { _, _ ->
+ val context = context
+ context.tryAsActivity()?.invalidateOptionsMenu()
+ }
+ }
+ }
+
+ fun atLeastOneEngineChecked(): Boolean {
+ for (i in 0 until searchEngineGroup!!.childCount) {
+ val engineButton = searchEngineGroup!!.getChildAt(i) as CompoundButton
+ if (engineButton.isChecked) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/RadioSearchEngineListPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/RadioSearchEngineListPreference.kt
new file mode 100644
index 0000000000..1c55ba0ca2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/RadioSearchEngineListPreference.kt
@@ -0,0 +1,64 @@
+/* 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.focus.search
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.CompoundButton
+import android.widget.RadioGroup
+import androidx.preference.PreferenceViewHolder
+import mozilla.components.browser.state.search.SearchEngine
+import org.mozilla.focus.GleanMetrics.SearchEngines
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+
+private const val ENGINE_TYPE_CUSTOM = "custom"
+private const val ENGINE_TYPE_BUNDLED = "bundled"
+
+class RadioSearchEngineListPreference : SearchEngineListPreference, RadioGroup.OnCheckedChangeListener {
+
+ override val itemResId: Int
+ get() = R.layout.search_engine_radio_button
+
+ @Suppress("unused")
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ @Suppress("unused")
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ searchEngineGroup!!.setOnCheckedChangeListener(this)
+ }
+
+ override fun updateDefaultItem(defaultButton: CompoundButton) {
+ defaultButton.isChecked = true
+ }
+
+ override fun onCheckedChanged(group: RadioGroup, checkedId: Int) {
+ val selectedEngine = group.getChildAt(checkedId) ?: return
+
+ // check if the corresponding button was pressed or a11y focused.
+ val hasProperState = selectedEngine.isPressed || selectedEngine.isAccessibilityFocused
+
+ /* onCheckedChanged is called intermittently before the search engine table is full, so we
+ must check these conditions to prevent crashes and inconsistent states. */
+ if (group.childCount != searchEngines.count() || !hasProperState) {
+ return
+ }
+
+ val newDefaultEngine = searchEngines[checkedId]
+
+ context.components.searchUseCases.selectSearchEngine(newDefaultEngine)
+
+ val source = if (newDefaultEngine.type == SearchEngine.Type.CUSTOM) {
+ ENGINE_TYPE_CUSTOM
+ } else {
+ ENGINE_TYPE_BUNDLED
+ }
+
+ SearchEngines.setDefault.record(SearchEngines.SetDefaultExtra(source))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEngineListPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEngineListPreference.kt
new file mode 100644
index 0000000000..9831534b8e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEngineListPreference.kt
@@ -0,0 +1,114 @@
+/* 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.focus.search
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.drawable.BitmapDrawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.CompoundButton
+import android.widget.RadioGroup
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import mozilla.components.browser.state.search.SearchEngine
+import mozilla.components.browser.state.state.searchEngines
+import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import kotlin.coroutines.CoroutineContext
+
+abstract class SearchEngineListPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : Preference(context, attrs, defStyleAttr), CoroutineScope {
+
+ private val job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+ protected var searchEngines: List = emptyList()
+ protected var searchEngineGroup: RadioGroup? = null
+
+ protected abstract val itemResId: Int
+
+ init {
+ layoutResource = R.layout.preference_search_engine_chooser
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ searchEngineGroup = holder.itemView.findViewById(R.id.search_engine_group)
+ val context = searchEngineGroup!!.context
+
+ searchEngines = context.components.store.state.search.searchEngines
+
+ refreshSearchEngineViews(context)
+ }
+
+ override fun onDetached() {
+ job.cancel()
+ super.onDetached()
+ }
+
+ protected abstract fun updateDefaultItem(defaultButton: CompoundButton)
+
+ fun refetchSearchEngines() {
+ searchEngines = context.components.store.state.search.searchEngines
+ refreshSearchEngineViews(this@SearchEngineListPreference.context)
+ }
+
+ private fun refreshSearchEngineViews(context: Context) {
+ if (searchEngineGroup == null) {
+ // We want to refresh the search engine list of this preference in onResume,
+ // but the first time this preference is created onResume is called before onCreateView
+ // so searchEngineGroup is not set yet.
+ return
+ }
+
+ val defaultSearchEngine =
+ context.components.store.state.search.selectedOrDefaultSearchEngine?.id
+
+ searchEngineGroup!!.removeAllViews()
+
+ val layoutInflater = LayoutInflater.from(context)
+ val layoutParams = RecyclerView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+
+ for (i in searchEngines.indices) {
+ val engine = searchEngines[i]
+ val engineId = engine.id
+ val engineItem = makeButtonFromSearchEngine(engine, layoutInflater, context.resources)
+ engineItem.id = i
+ engineItem.tag = engineId
+ if (engineId == defaultSearchEngine) {
+ updateDefaultItem(engineItem)
+ }
+ searchEngineGroup!!.addView(engineItem, layoutParams)
+ }
+ }
+
+ private fun makeButtonFromSearchEngine(
+ engine: SearchEngine,
+ layoutInflater: LayoutInflater,
+ res: Resources,
+ ): CompoundButton {
+ val buttonItem = layoutInflater.inflate(itemResId, null) as CompoundButton
+ buttonItem.text = engine.name
+ val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt()
+ val engineIcon = BitmapDrawable(res, engine.icon)
+ engineIcon.setBounds(0, 0, iconSize, iconSize)
+ val drawables = buttonItem.compoundDrawables
+ buttonItem.setCompoundDrawablesRelative(engineIcon, null, drawables[2], null)
+ return buttonItem
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEnginePreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEnginePreference.kt
new file mode 100644
index 0000000000..257f126628
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchEnginePreference.kt
@@ -0,0 +1,48 @@
+/* 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.focus.search
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.AttributeSet
+import androidx.preference.Preference
+import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+
+/**
+ * Preference for setting the default search engine.
+ */
+class SearchEnginePreference : Preference, SharedPreferences.OnSharedPreferenceChangeListener {
+ internal val context: Context
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ this.context = context
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ this.context = context
+ }
+
+ override fun onAttached() {
+ summary = defaultSearchEngineName
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ super.onAttached()
+ }
+
+ override fun onPrepareForRemoval() {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPrepareForRemoval()
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ if (key == context.resources.getString(R.string.pref_key_search_engine)) {
+ summary = defaultSearchEngineName
+ }
+ }
+
+ private val defaultSearchEngineName: String
+ get() = context.components.store.state.search.selectedOrDefaultSearchEngine?.name ?: ""
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchFilterMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchFilterMiddleware.kt
new file mode 100644
index 0000000000..a66c8dc7b5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchFilterMiddleware.kt
@@ -0,0 +1,37 @@
+/* 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.focus.search
+
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.SearchAction
+import mozilla.components.browser.state.search.SearchEngine
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+
+/**
+ * [Middleware] for modifying the loaded list of [SearchEngine]s.
+ */
+class SearchFilterMiddleware : Middleware {
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ if (action is SearchAction.SetSearchEnginesAction) {
+ val newAction = action.copy(
+ regionSearchEngines = action.regionSearchEngines.filterBing(),
+ )
+
+ next(newAction)
+ } else {
+ next(action)
+ }
+ }
+}
+
+private fun List.filterBing(): List {
+ return filter { searchEngine -> searchEngine.id != "bing" }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchMigration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchMigration.kt
new file mode 100644
index 0000000000..36a53fc1ba
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/search/SearchMigration.kt
@@ -0,0 +1,71 @@
+/* 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.focus.search
+
+import android.content.Context
+import android.content.SharedPreferences
+import mozilla.components.browser.state.search.SearchEngine
+import mozilla.components.feature.search.ext.parseLegacySearchEngine
+import mozilla.components.feature.search.middleware.SearchMiddleware
+import org.mozilla.focus.ext.settings
+import org.xmlpull.v1.XmlPullParserException
+import java.io.BufferedInputStream
+import java.io.IOException
+
+private const val PREF_FILE_SEARCH_ENGINES = "custom-search-engines"
+private const val PREF_KEY_MIGRATED = "pref_search_migrated"
+private const val PREF_KEY_CUSTOM_SEARCH_ENGINES = "pref_custom_search_engines"
+
+/**
+ * Helper class to migrate the search related data in Focus to the "Android Components" implementation.
+ */
+class SearchMigration(
+ private val context: Context,
+) : SearchMiddleware.Migration {
+
+ override fun getValuesToMigrate(): SearchMiddleware.Migration.MigrationValues? {
+ val preferences = context.getSharedPreferences(PREF_FILE_SEARCH_ENGINES, Context.MODE_PRIVATE)
+ if (preferences.getBoolean(PREF_KEY_MIGRATED, false)) {
+ return null
+ }
+
+ @Suppress("DEPRECATION")
+ val values = SearchMiddleware.Migration.MigrationValues(
+ customSearchEngines = loadCustomSearchEngines(preferences),
+ defaultSearchEngineName = context.settings.defaultSearchEngineName,
+ )
+
+ preferences.edit()
+ .putBoolean(PREF_KEY_MIGRATED, true)
+ .apply()
+
+ return values
+ }
+
+ private fun loadCustomSearchEngines(
+ preferences: SharedPreferences,
+ ): List {
+ val engines = preferences.getStringSet(PREF_KEY_CUSTOM_SEARCH_ENGINES, emptySet())!!
+
+ return engines.mapNotNull { engine ->
+ val engineInputStream = preferences.getString(engine, "")!!
+ .byteInputStream()
+ .buffered()
+
+ loadSafely(engine, engineInputStream)
+ }
+ }
+}
+
+@Suppress("SwallowedException", "DEPRECATION")
+private fun loadSafely(id: String, stream: BufferedInputStream?): SearchEngine? {
+ return try {
+ stream?.let { parseLegacySearchEngine(id, it) }
+ } catch (e: IOException) {
+ null
+ } catch (e: XmlPullParserException) {
+ null
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsPreferences.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsPreferences.kt
new file mode 100644
index 0000000000..3c1b36e2e7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsPreferences.kt
@@ -0,0 +1,44 @@
+/* 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.focus.searchsuggestions
+
+import android.content.Context
+import androidx.preference.PreferenceManager
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.settings
+
+class SearchSuggestionsPreferences(private val context: Context) {
+ private val settings = context.settings
+ private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+ fun searchSuggestionsEnabled(): Boolean = settings.shouldShowSearchSuggestions()
+ fun hasUserToggledSearchSuggestions(): Boolean = settings.userHasToggledSearchSuggestions()
+ fun userHasDismissedNoSuggestionsMessage(): Boolean = settings.userHasDismissedNoSuggestionsMessage()
+
+ fun enableSearchSuggestions() {
+ preferences.edit()
+ .putBoolean(TOGGLED_SUGGESTIONS_PREF, true)
+ .putBoolean(context.resources.getString(R.string.pref_key_show_search_suggestions), true)
+ .apply()
+ }
+
+ fun disableSearchSuggestions() {
+ preferences.edit()
+ .putBoolean(TOGGLED_SUGGESTIONS_PREF, true)
+ .putBoolean(context.resources.getString(R.string.pref_key_show_search_suggestions), false)
+ .apply()
+ }
+
+ fun dismissNoSuggestionsMessage() {
+ preferences.edit()
+ .putBoolean(DISMISSED_NO_SUGGESTIONS_PREF, true)
+ .apply()
+ }
+
+ companion object {
+ const val TOGGLED_SUGGESTIONS_PREF = "user_has_toggled_search_suggestions"
+ const val DISMISSED_NO_SUGGESTIONS_PREF = "user_dismissed_no_search_suggestions"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModel.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModel.kt
new file mode 100644
index 0000000000..7a873ba816
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModel.kt
@@ -0,0 +1,116 @@
+/* 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.focus.searchsuggestions
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
+import mozilla.components.feature.search.ext.canProvideSearchSuggestions
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.FocusApplication
+import org.mozilla.focus.GleanMetrics.SearchSuggestions
+
+sealed class State {
+ data class Disabled(val givePrompt: Boolean) : State()
+ data class NoSuggestionsAPI(val givePrompt: Boolean) : State()
+ object ReadyForSuggestions : State()
+}
+
+class SearchSuggestionsViewModel(application: Application) : AndroidViewModel(application) {
+ private val preferences: SearchSuggestionsPreferences = SearchSuggestionsPreferences(application)
+
+ private val _selectedSearchSuggestion = MutableLiveData()
+ val selectedSearchSuggestion: LiveData = _selectedSearchSuggestion
+
+ private val _searchQuery = MutableLiveData()
+ val searchQuery: LiveData = _searchQuery
+
+ private val _state = MutableLiveData()
+ val state: LiveData = _state
+
+ private val _autocompleteSuggestion = MutableLiveData()
+ val autocompleteSuggestion: LiveData = _autocompleteSuggestion
+
+ var alwaysSearch = false
+ private set
+
+ fun selectSearchSuggestion(
+ suggestion: String,
+ defaultSearchEngineName: String,
+ alwaysSearch: Boolean = false,
+ ) {
+ this.alwaysSearch = alwaysSearch
+ _selectedSearchSuggestion.postValue(suggestion)
+
+ if (suggestion == searchQuery.value) {
+ SearchSuggestions.searchTapped.record(
+ SearchSuggestions.SearchTappedExtra(defaultSearchEngineName),
+ )
+ } else {
+ SearchSuggestions.suggestionTapped.record(
+ SearchSuggestions.SuggestionTappedExtra(defaultSearchEngineName),
+ )
+ }
+ }
+
+ fun clearSearchSuggestion() {
+ _selectedSearchSuggestion.postValue(null)
+ }
+
+ fun setAutocompleteSuggestion(text: String) {
+ _autocompleteSuggestion.postValue(text)
+ SearchSuggestions.autocompleteArrowTapped.record(NoExtras())
+ }
+
+ fun clearAutocompleteSuggestion() {
+ _autocompleteSuggestion.postValue(null)
+ }
+
+ fun setSearchQuery(query: String) {
+ _searchQuery.value = query
+ }
+
+ fun enableSearchSuggestions() {
+ preferences.enableSearchSuggestions()
+ updateState()
+ setSearchQuery(searchQuery.value ?: "")
+ }
+
+ fun disableSearchSuggestions() {
+ preferences.disableSearchSuggestions()
+ updateState()
+ }
+
+ fun dismissNoSuggestionsMessage() {
+ preferences.dismissNoSuggestionsMessage()
+ updateState()
+ }
+
+ fun refresh() {
+ updateState()
+ }
+
+ private fun updateState() {
+ val enabled = preferences.searchSuggestionsEnabled()
+
+ val store = getApplication().components.store
+
+ val state = if (enabled) {
+ if (store.state.search.selectedOrDefaultSearchEngine?.canProvideSearchSuggestions == true) {
+ State.ReadyForSuggestions
+ } else {
+ val givePrompt = !preferences.userHasDismissedNoSuggestionsMessage()
+ State.NoSuggestionsAPI(givePrompt)
+ }
+ } else {
+ val givePrompt = !preferences.hasUserToggledSearchSuggestions()
+ State.Disabled(givePrompt)
+ }
+
+ _state.value = state
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchOverlay.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchOverlay.kt
new file mode 100644
index 0000000000..1e49be666d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchOverlay.kt
@@ -0,0 +1,121 @@
+/* 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.focus.searchsuggestions.ui
+
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.graphics.drawable.toBitmap
+import kotlinx.coroutines.DelicateCoroutinesApi
+import mozilla.components.compose.browser.awesomebar.AwesomeBar
+import mozilla.components.compose.browser.awesomebar.AwesomeBarDefaults
+import mozilla.components.concept.awesomebar.AwesomeBar
+import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider
+import org.mozilla.focus.R
+import org.mozilla.focus.components
+import org.mozilla.focus.searchsuggestions.SearchSuggestionsViewModel
+import org.mozilla.focus.searchsuggestions.State
+import org.mozilla.focus.topsites.TopSitesOverlay
+import org.mozilla.focus.ui.theme.focusColors
+
+@OptIn(DelicateCoroutinesApi::class)
+@Composable
+fun SearchOverlay(
+ viewModel: SearchSuggestionsViewModel,
+ defaultSearchEngineName: String,
+ onListScrolled: () -> Unit,
+) {
+ val state = viewModel.state.observeAsState()
+ val query = viewModel.searchQuery.observeAsState()
+
+ when (state.value) {
+ is State.Disabled,
+ is State.NoSuggestionsAPI,
+ -> {
+ if (query.value.isNullOrEmpty()) {
+ TopSitesOverlay(modifier = Modifier.background(focusColors.surface))
+ }
+ }
+ is State.ReadyForSuggestions -> {
+ if (query.value.isNullOrEmpty()) {
+ TopSitesOverlay(modifier = Modifier.background(focusColors.surface))
+ } else {
+ SearchSuggestions(
+ text = query.value ?: "",
+ onSuggestionClicked = { suggestion ->
+ viewModel.selectSearchSuggestion(
+ suggestion.title!!,
+ defaultSearchEngineName,
+ )
+ },
+ onAutoComplete = { suggestion ->
+ val editSuggestion = suggestion.editSuggestion ?: return@SearchSuggestions
+ viewModel.setAutocompleteSuggestion(editSuggestion)
+ },
+ onListScrolled = onListScrolled,
+ )
+ }
+ }
+ else -> {
+ // no-op
+ }
+ }
+}
+
+@Composable
+private fun SearchSuggestions(
+ text: String,
+ onSuggestionClicked: (AwesomeBar.Suggestion) -> Unit,
+ onAutoComplete: (AwesomeBar.Suggestion) -> Unit,
+ onListScrolled: () -> Unit,
+) {
+ val context = LocalContext.current
+ val components = components
+
+ val icon = AppCompatResources.getDrawable(context, R.drawable.mozac_ic_search_24)?.toBitmap()
+ val provider = remember(context) {
+ SearchSuggestionProvider(
+ context,
+ components.store,
+ components.searchUseCases.newPrivateTabSearch,
+ components.client,
+ mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
+ private = true,
+ showDescription = false,
+ icon = icon,
+ )
+ }
+
+ val nestedScrollConnection = remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ onListScrolled.invoke()
+ return Offset.Zero
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier.nestedScroll(nestedScrollConnection),
+ ) {
+ AwesomeBar(
+ text = text,
+ colors = AwesomeBarDefaults.colors(
+ background = focusColors.surface,
+ ),
+ providers = listOf(provider),
+ onSuggestionClicked = onSuggestionClicked,
+ onAutoComplete = onAutoComplete,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsFragment.kt
new file mode 100644
index 0000000000..4f0c2f5bdb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsFragment.kt
@@ -0,0 +1,127 @@
+/* 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.focus.searchsuggestions.ui
+
+import android.os.Bundle
+import android.text.method.LinkMovementMethod
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.ktx.android.view.hideKeyboard
+import org.mozilla.focus.GleanMetrics.ShowSearchSuggestions
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentSearchSuggestionsBinding
+import org.mozilla.focus.ext.defaultSearchEngineName
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.searchsuggestions.SearchSuggestionsViewModel
+import org.mozilla.focus.searchsuggestions.State
+import org.mozilla.focus.ui.theme.FocusTheme
+import kotlin.coroutines.CoroutineContext
+
+class SearchSuggestionsFragment : Fragment(), CoroutineScope {
+ private var job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ private var _binding: FragmentSearchSuggestionsBinding? = null
+ private val binding get() = _binding!!
+
+ private val defaultSearchEngineName: String
+ get() = requireComponents.store.defaultSearchEngineName()
+
+ private val searchSuggestionsViewModel: SearchSuggestionsViewModel by activityViewModels()
+
+ override fun onResume() {
+ super.onResume()
+
+ if (job.isCancelled) {
+ job = Job()
+ }
+
+ searchSuggestionsViewModel.refresh()
+ }
+
+ override fun onPause() {
+ job.cancel()
+ super.onPause()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentSearchSuggestionsBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ searchSuggestionsViewModel.state.observe(
+ viewLifecycleOwner,
+ ) { state ->
+ binding.enableSearchSuggestionsContainer.isVisible = false
+ binding.noSuggestionsContainer.isVisible = false
+
+ when (state) {
+ is State.ReadyForSuggestions -> { // Handled by Jetpack Compose implementation
+ }
+ is State.NoSuggestionsAPI ->
+ binding.noSuggestionsContainer.isVisible = state.givePrompt
+ is State.Disabled ->
+ binding.enableSearchSuggestionsContainer.isVisible = state.givePrompt
+ }
+ }
+
+ val appName = resources.getString(R.string.app_name)
+
+ binding.enableSearchSuggestionsSubtitle.apply {
+ text = resources.getString(R.string.enable_search_suggestion_description, appName)
+ movementMethod = LinkMovementMethod.getInstance()
+ highlightColor = android.graphics.Color.TRANSPARENT
+ }
+ binding.enableSearchSuggestionsButton.setOnClickListener {
+ searchSuggestionsViewModel.enableSearchSuggestions()
+ ShowSearchSuggestions.enabledFromPanel.record(NoExtras())
+ }
+
+ binding.disableSearchSuggestionsButton.setOnClickListener {
+ searchSuggestionsViewModel.disableSearchSuggestions()
+ ShowSearchSuggestions.disabledFromPanel.record(NoExtras())
+ }
+
+ binding.dismissNoSuggestionsMessage.setOnClickListener {
+ searchSuggestionsViewModel.dismissNoSuggestionsMessage()
+ }
+
+ val searchSuggestionsView = view.findViewById(R.id.search_suggestions_view)
+ searchSuggestionsView.setContent {
+ FocusTheme {
+ SearchOverlay(
+ searchSuggestionsViewModel,
+ defaultSearchEngineName,
+ ) { view.hideKeyboard() }
+ }
+ }
+ }
+
+ companion object {
+ fun create() = SearchSuggestionsFragment()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsPreference.kt
new file mode 100644
index 0000000000..a11fcf25e1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchsuggestions/ui/SearchSuggestionsPreference.kt
@@ -0,0 +1,31 @@
+/* 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.focus.searchsuggestions.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import org.mozilla.focus.R
+import org.mozilla.focus.settings.LearnMoreSwitchPreference
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * Switch preference for enabling/disabling autocompletion for default domains that ship with the app.
+ */
+class SearchSuggestionsPreference(
+ context: Context,
+ attrs: AttributeSet?,
+) : LearnMoreSwitchPreference(context, attrs) {
+ override fun getLearnMoreUrl(): String =
+ SupportUtils.getSumoURLForTopic(
+ SupportUtils.getAppVersion(context),
+ SupportUtils.SumoTopic.SEARCH_SUGGESTIONS,
+ )
+
+ override fun getDescription(): String =
+ context.getString(
+ R.string.preference_show_search_suggestions_summary,
+ context.getString(R.string.app_name),
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/ExternalIntentNavigation.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/ExternalIntentNavigation.kt
new file mode 100644
index 0000000000..c900c2b17c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/ExternalIntentNavigation.kt
@@ -0,0 +1,125 @@
+/* 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.focus.searchwidget
+
+import android.content.Context
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.SearchWidget
+import org.mozilla.focus.activity.IntentReceiverActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.perf.Performance
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.utils.SearchUtils
+
+/**
+ * Handles all actions from outside the app that starts it.
+ */
+object ExternalIntentNavigation {
+
+ /**
+ * Handle all the navigation from search widget, app icon, lockscreen and custom tab.
+ *
+ * @param bundle External Intent [Bundle] with data based on which destination will be computed.
+ * @param context Android [Context] used for system interactions or accessing dependencies.
+ */
+ fun handleAppNavigation(
+ bundle: Bundle?,
+ context: Context,
+ ) {
+ if (handleWidgetVoiceSearch(bundle, context)) return
+
+ if (handleWidgetTextSearch(bundle, context)) return
+
+ if (handleBrowserTabAlreadyOpen(context)) return
+
+ handleAppOpened(bundle, context)
+ }
+
+ /**
+ * Handle the app being opened with no specified destination.
+ * This can show the onboarding or the app's home screen.
+ */
+ @VisibleForTesting
+ internal fun handleAppOpened(
+ bundle: Bundle?,
+ context: Context,
+ ) {
+ if (context.settings.isFirstRun &&
+ !Performance.processIntentIfPerformanceTest(bundle, context)
+ ) {
+ context.components.appStore.dispatch(AppAction.ShowFirstRun)
+ }
+ }
+
+ /**
+ * Try navigating to the currently selected tab.
+ *
+ * @return Whether or not the app navigated to the currently selected tab.
+ */
+ @VisibleForTesting
+ internal fun handleBrowserTabAlreadyOpen(context: Context): Boolean {
+ val tabId = context.components.store.state.selectedTabId
+
+ return when (tabId != null) {
+ true -> {
+ context.components.appStore.dispatch(AppAction.OpenTab(tabId))
+ true
+ }
+ else -> false
+ }
+ }
+
+ /**
+ * Try navigating to search by text.
+ *
+ * @return Whether or not a widget interaction was detected and the app opened the search screen for it.
+ */
+ @VisibleForTesting
+ internal fun handleWidgetTextSearch(bundle: Bundle?, context: Context): Boolean {
+ val searchWidgetIntent = bundle?.getBoolean(IntentReceiverActivity.SEARCH_WIDGET_EXTRA, false)
+
+ return when (searchWidgetIntent == true) {
+ true -> {
+ SearchWidget.newTabButton.record(NoExtras())
+ context.components.appStore.dispatch(AppAction.ShowHomeScreen)
+ true
+ }
+ else -> false
+ }
+ }
+
+ /**
+ * Try opening a new tab for the user voice search.
+ *
+ * @return Whether or not a voice search was identified and the app opened a new tab for it.
+ */
+ @VisibleForTesting
+ internal fun handleWidgetVoiceSearch(bundle: Bundle?, context: Context): Boolean {
+ val voiceSearchText = bundle?.getString(BaseVoiceSearchActivity.SPEECH_PROCESSING)
+
+ return when (!voiceSearchText.isNullOrEmpty()) {
+ true -> {
+ val tabId = context.components.tabsUseCases.addTab(
+ url = SearchUtils.createSearchUrl(
+ context,
+ voiceSearchText,
+ ),
+ source = SessionState.Source.External.ActionSend(null),
+ searchTerms = voiceSearchText,
+ selectTab = true,
+ private = true,
+ )
+ context.components.appStore.dispatch(AppAction.OpenTab(tabId))
+ true
+ }
+ else -> false
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt
new file mode 100644
index 0000000000..ca42e12be5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt
@@ -0,0 +1,201 @@
+/* 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.focus.searchwidget
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import androidx.constraintlayout.compose.ConstraintLayout
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+import org.mozilla.focus.ui.theme.focusTypography
+
+@Composable
+@Preview
+private fun PromoteSearchWidgetDialogComposePreview() {
+ FocusTheme {
+ PromoteSearchWidgetDialogCompose({}, {})
+ }
+}
+
+@Suppress("LongMethod")
+@Composable
+fun PromoteSearchWidgetDialogCompose(
+ onAddSearchWidgetButtonClick: () -> Unit,
+ onDismiss: () -> Unit,
+) {
+ val openDialog = remember { mutableStateOf(true) }
+ if (openDialog.value) {
+ Dialog(
+ onDismissRequest = {
+ onDismiss()
+ },
+ DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = false),
+ ) {
+ Column(
+ modifier = Modifier
+ .clip(RoundedCornerShape(20.dp)),
+ ) {
+ ConstraintLayout(
+ modifier = Modifier
+ .wrapContentSize()
+ .background(
+ colorResource(id = R.color.promote_search_widget_dialog_background),
+ ),
+ ) {
+ val (closeButton, content) = createRefs()
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(top = 8.dp, end = 16.dp)
+ .constrainAs(closeButton) {
+ top.linkTo(parent.top)
+ start.linkTo(parent.start)
+ end.linkTo(parent.end)
+ },
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.End,
+ ) {
+ CloseButton(openDialog, onDismiss)
+ }
+ Column(
+ modifier = Modifier.constrainAs(content) {
+ top.linkTo(closeButton.bottom)
+ start.linkTo(parent.start)
+ end.linkTo(parent.end)
+ },
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(
+ id = R.string.promote_search_widget_dialog_title,
+ ),
+ modifier = Modifier
+ .padding(16.dp),
+ color = focusColors.dialogTextColor,
+ textAlign = TextAlign.Center,
+ style = focusTypography.dialogTitle,
+ )
+ Text(
+ text = stringResource(
+ id = R.string.promote_search_widget_dialog_subtitle,
+ LocalContext.current.getString(R.string.onboarding_short_app_name),
+ ),
+ modifier = Modifier
+ .padding(top = 16.dp, start = 16.dp, end = 16.dp),
+ color = focusColors.dialogTextColor,
+ textAlign = TextAlign.Center,
+ style = focusTypography.dialogContent,
+ )
+ Image(
+ painter = painterResource(R.drawable.focus_search_widget_promote_dialog),
+ contentDescription = LocalContext.current.getString(
+ R.string.promote_search_widget_dialog_picture_content_description,
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 10.dp, end = 10.dp)
+ .background(
+ colorResource(id = R.color.promote_search_widget_dialog_background),
+ ),
+ )
+ ComponentAddWidgetButton({ onAddSearchWidgetButtonClick() }, { onDismiss() }, openDialog)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ComponentAddWidgetButton(
+ onAddSearchWidgetButtonClick: () -> Unit,
+ onDismiss: () -> Unit,
+ openState: MutableState,
+) {
+ Button(
+ onClick = {
+ openState.value = false
+ onAddSearchWidgetButtonClick()
+ onDismiss()
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(24.dp),
+ shape = RoundedCornerShape(8.dp),
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = colorResource(R.color.promote_search_widget_dialog_add_widget_button_background),
+ ),
+ ) {
+ Text(
+ text = AnnotatedString(
+ LocalContext.current.resources.getString(
+ R.string.promote_search_widget_button_text,
+ ),
+ ),
+ color = PhotonColors.White,
+ )
+ }
+}
+
+@Composable
+private fun CloseButton(
+ openState: MutableState,
+ onDismiss: () -> Unit,
+) {
+ IconButton(
+ onClick = {
+ onDismiss()
+ openState.value = false
+ },
+ modifier = Modifier
+ .background(
+ colorResource(id = R.color.promote_search_widget_dialog_close_button_background),
+ shape = CircleShape,
+ )
+ .size(48.dp)
+ .padding(10.dp),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.mozac_ic_cross_24),
+ contentDescription = stringResource(id = R.string.promote_search_widget_dialog_content_description),
+ tint = focusColors.closeIcon,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetProvider.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetProvider.kt
new file mode 100644
index 0000000000..cbd597a51f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetProvider.kt
@@ -0,0 +1,73 @@
+/* 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.focus.searchwidget
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.speech.RecognizerIntent
+import androidx.annotation.VisibleForTesting
+import mozilla.components.feature.search.widget.AppSearchWidgetProvider
+import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
+import mozilla.components.feature.search.widget.SearchWidgetConfig
+import mozilla.components.support.utils.PendingIntentUtils
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.IntentReceiverActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.session.VisibilityLifeCycleCallback
+import org.mozilla.focus.state.AppAction
+
+class SearchWidgetProvider : AppSearchWidgetProvider() {
+
+ override fun onEnabled(context: Context) {
+ context.components.settings.addSearchWidgetInstalled(1)
+
+ // The snackBar that informs the user that search widget was added successfully
+ // should appear only if the app is in foreground
+ if (!VisibilityLifeCycleCallback.isInBackground(context)) {
+ context.components.appStore.dispatch(AppAction.ShowSearchWidgetSnackBar(true))
+ }
+ }
+
+ override fun onDeleted(context: Context, appWidgetIds: IntArray) {
+ context.components.settings.addSearchWidgetInstalled(-appWidgetIds.size)
+ }
+
+ override val config: SearchWidgetConfig =
+ SearchWidgetConfig(
+ searchWidgetIconResource = R.drawable.ic_splash_screen,
+ searchWidgetMicrophoneResource = R.drawable.mozac_ic_microphone_24,
+ appName = R.string.app_name,
+ )
+
+ override fun createTextSearchIntent(context: Context): PendingIntent {
+ val textSearchIntent = Intent(context, IntentReceiverActivity::class.java)
+ .apply {
+ this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ this.putExtra(IntentReceiverActivity.SEARCH_WIDGET_EXTRA, true)
+ }
+ return PendingIntent.getActivity(
+ context,
+ REQUEST_CODE_NEW_TAB,
+ textSearchIntent,
+ PendingIntentUtils.defaultFlags or
+ PendingIntent.FLAG_UPDATE_CURRENT,
+ )
+ }
+
+ override fun shouldShowVoiceSearch(context: Context): Boolean {
+ val intentSpeech = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
+ return intentSpeech.resolveActivity(context.packageManager) != null
+ }
+
+ override fun voiceSearchActivity(): Class {
+ return VoiceSearchActivity::class.java
+ }
+
+ companion object {
+ @VisibleForTesting
+ const val REQUEST_CODE_NEW_TAB = 0
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetUtils.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetUtils.kt
new file mode 100644
index 0000000000..2116dd19dd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/SearchWidgetUtils.kt
@@ -0,0 +1,73 @@
+/* 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.focus.searchwidget
+
+import android.app.Activity
+import android.app.Dialog
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import mozilla.components.support.utils.PendingIntentUtils
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.SearchWidget
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.ui.theme.FocusTheme
+
+object SearchWidgetUtils {
+
+ private fun addSearchWidgetToHomeScreen(activity: Activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val appWidgetManager = AppWidgetManager.getInstance(activity)
+ val searchWidgetProvider = ComponentName(activity, SearchWidgetProvider::class.java)
+ if (appWidgetManager!!.isRequestPinAppWidgetSupported) {
+ val pinnedWidgetCallbackIntent = Intent(activity, SearchWidgetProvider::class.java)
+ val successCallback = PendingIntent.getBroadcast(
+ activity,
+ 0,
+ pinnedWidgetCallbackIntent,
+ PendingIntentUtils.defaultFlags or
+ PendingIntent.FLAG_UPDATE_CURRENT,
+ )
+ appWidgetManager.requestPinAppWidget(searchWidgetProvider, Bundle(), successCallback)
+ }
+ }
+ }
+
+ /**
+ * Shows promote search widget dialog
+ */
+ fun showPromoteSearchWidgetDialog(activity: MainActivity) {
+ val promoteSearchWidgetDialog = Dialog(activity)
+ promoteSearchWidgetDialog.apply {
+ setContentView(
+ ComposeView(activity).apply {
+ setViewTreeLifecycleOwner(activity)
+ this.setViewTreeSavedStateRegistryOwner(activity)
+ setContent {
+ FocusTheme {
+ PromoteSearchWidgetDialogCompose(
+ onAddSearchWidgetButtonClick = {
+ addSearchWidgetToHomeScreen(activity)
+ SearchWidget.addToHomeScreenButton.record(NoExtras())
+ },
+ onDismiss = {
+ promoteSearchWidgetDialog.dismiss()
+ },
+ )
+ }
+ }
+ isTransitionGroup = true
+ },
+ )
+ }.show()
+ SearchWidget.promoteDialogShown.record(NoExtras())
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/VoiceSearchActivity.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/VoiceSearchActivity.kt
new file mode 100644
index 0000000000..20d0ff6124
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/VoiceSearchActivity.kt
@@ -0,0 +1,32 @@
+/* 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.focus.searchwidget
+
+import android.content.Intent
+import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
+import mozilla.components.support.locale.LocaleManager
+import mozilla.components.support.locale.LocaleManager.getCurrentLocale
+import mozilla.telemetry.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.SearchWidget
+import org.mozilla.focus.activity.IntentReceiverActivity
+import java.util.Locale
+
+class VoiceSearchActivity : BaseVoiceSearchActivity() {
+
+ override fun getCurrentLocale(): Locale {
+ return getCurrentLocale(this)
+ ?: LocaleManager.getSystemDefault()
+ }
+
+ override fun onSpeechRecognitionEnded(spokenText: String) {
+ val intent = Intent(this, IntentReceiverActivity::class.java)
+ intent.putExtra(SPEECH_PROCESSING, spokenText)
+ startActivity(intent)
+ }
+
+ override fun onSpeechRecognitionStarted() {
+ SearchWidget.voiceButton.record(NoExtras())
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/IntentProcessor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/IntentProcessor.kt
new file mode 100644
index 0000000000..0975e7970c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/IntentProcessor.kt
@@ -0,0 +1,203 @@
+/* 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.focus.session
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.text.TextUtils
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.feature.customtabs.createCustomTabConfigFromIntent
+import mozilla.components.feature.customtabs.isCustomTabIntent
+import mozilla.components.feature.tabs.CustomTabsUseCases
+import mozilla.components.feature.tabs.TabsUseCases
+import mozilla.components.support.ktx.util.URLStringUtils
+import mozilla.components.support.utils.SafeIntent
+import mozilla.components.support.utils.WebURLFinder
+import org.mozilla.focus.activity.TextActionActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.shortcut.HomeScreen
+import org.mozilla.focus.utils.SearchUtils
+
+/**
+ * Implementation moved from Focus SessionManager. To be replaced with SessionIntentProcessor from feature-session
+ * component soon.
+ */
+class IntentProcessor(
+ private val context: Context,
+ private val tabsUseCases: TabsUseCases,
+ private val customTabsUseCases: CustomTabsUseCases,
+) {
+ sealed class Result {
+ object None : Result()
+ data class Tab(val id: String) : Result()
+ data class CustomTab(val id: String) : Result()
+ }
+
+ /**
+ * Handle this incoming intent (via onCreate()) and create a new session if required.
+ */
+ fun handleIntent(context: Context, intent: SafeIntent, savedInstanceState: Bundle?): Result {
+ if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
+ // This Intent was launched from history (recent apps). Android will redeliver the
+ // original Intent (which might be a VIEW intent). However if there's no active browsing
+ // session then we do not want to re-process the Intent and potentially re-open a website
+ // from a session that the user already "erased".
+ return Result.None
+ }
+
+ if (savedInstanceState != null) {
+ // We are restoring a previous session - No need to handle this Intent.
+ return Result.None
+ }
+
+ return createSessionFromIntent(context, intent)
+ }
+
+ /**
+ * Handle this incoming intent (via onNewIntent()) and create a new session if required.
+ */
+ fun handleNewIntent(context: Context, intent: SafeIntent) {
+ createSessionFromIntent(context, intent)
+ }
+
+ @Suppress("ComplexMethod", "ReturnCount")
+ private fun createSessionFromIntent(context: Context, intent: SafeIntent): Result {
+ when (intent.action) {
+ Intent.ACTION_VIEW -> {
+ val dataString = intent.dataString
+ if (TextUtils.isEmpty(dataString)) {
+ // If there's no URL in the Intent then we can't create a session.
+ return Result.None
+ }
+
+ return when {
+ intent.hasExtra(HomeScreen.ADD_TO_HOMESCREEN_TAG) -> {
+ val requestDesktop =
+ intent.getBooleanExtra(HomeScreen.REQUEST_DESKTOP, false)
+
+ // Ignoring, because exception!
+ // HomeScreen.BLOCKING_ENABLED
+
+ createSession(
+ SessionState.Source.Internal.HomeScreen,
+ intent,
+ intent.dataString ?: "",
+ requestDesktop,
+ )
+ }
+ intent.hasExtra(TextActionActivity.EXTRA_TEXT_SELECTION) -> createSession(
+ SessionState.Source.Internal.TextSelection,
+ intent,
+ intent.dataString ?: "",
+ )
+ else -> createSession(
+ SessionState.Source.External.ActionView(null),
+ intent,
+ intent.dataString ?: "",
+ )
+ }
+ }
+
+ Intent.ACTION_SEND -> {
+ val dataString = intent.getStringExtra(Intent.EXTRA_TEXT)
+ if (TextUtils.isEmpty(dataString)) {
+ return Result.None
+ }
+
+ return if (dataString == null || !URLStringUtils.isURLLike(dataString)) {
+ val bestURL = WebURLFinder(dataString).bestWebURL()
+ if (!TextUtils.isEmpty(bestURL)) {
+ createSession(SessionState.Source.External.ActionSend(null), bestURL ?: "")
+ } else {
+ createSearchSession(
+ SessionState.Source.External.ActionSend(null),
+ SearchUtils.createSearchUrl(context, dataString ?: ""),
+ dataString ?: "",
+ )
+ }
+ } else {
+ createSession(SessionState.Source.External.ActionSend(null), dataString)
+ }
+ }
+
+ else -> return Result.None
+ }
+ }
+
+ private fun createSession(source: SessionState.Source, url: String): Result {
+ return Result.Tab(
+ tabsUseCases.addTab(
+ url,
+ source = source,
+ selectTab = true,
+ private = true,
+ ),
+ )
+ }
+
+ private fun createSearchSession(source: SessionState.Source, url: String, searchTerms: String): Result {
+ return Result.Tab(
+ tabsUseCases.addTab(
+ url,
+ source = source,
+ searchTerms = searchTerms,
+ private = true,
+ ),
+ )
+ }
+
+ private fun createSession(source: SessionState.Source, intent: SafeIntent, url: String): Result {
+ return if (isCustomTabIntent(intent.unsafe)) {
+ Result.CustomTab(
+ customTabsUseCases.add(
+ url,
+ createCustomTabConfigFromIntent(intent.unsafe, context.resources),
+ private = true,
+ source = source,
+ ),
+ )
+ } else {
+ Result.Tab(
+ tabsUseCases.addTab(
+ url,
+ source = source,
+ selectTab = true,
+ private = true,
+ ),
+ )
+ }
+ }
+
+ private fun createSession(
+ source: SessionState.Source,
+ intent: SafeIntent,
+ url: String,
+ requestDesktop: Boolean,
+ ): Result {
+ val (result, tabId) = if (isCustomTabIntent(intent)) {
+ val tabId = customTabsUseCases.add(
+ url,
+ createCustomTabConfigFromIntent(intent.unsafe, context.resources),
+ private = true,
+ source = source,
+ )
+ Pair(Result.CustomTab(tabId), tabId)
+ } else {
+ val tabId = tabsUseCases.addTab(
+ url,
+ source = source,
+ private = true,
+ )
+ Pair(Result.Tab(tabId), tabId)
+ }
+
+ if (requestDesktop) {
+ context.components.sessionUseCases.requestDesktopSite(requestDesktop, tabId)
+ }
+
+ return result
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/PrivateNotificationFeature.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/PrivateNotificationFeature.kt
new file mode 100644
index 0000000000..1b2aaa22e6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/PrivateNotificationFeature.kt
@@ -0,0 +1,49 @@
+/* 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.focus.session
+
+import android.content.Context
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.support.base.feature.LifecycleAwareFeature
+
+/**
+ * Responsible for starting or stopping up a [SessionNotificationService]
+ * depending on whether a private tab is opened.
+ *
+ * @param browserStore Browser store reference used to observe the number of private tabs.
+ */
+class PrivateNotificationFeature(
+ context: Context,
+ private val browserStore: BrowserStore,
+ private val permissionRequestHandler: (() -> Unit),
+) : LifecycleAwareFeature {
+
+ private val applicationContext = context.applicationContext
+ private var scope: CoroutineScope? = null
+
+ override fun start() {
+ scope = browserStore.flowScoped { flow ->
+ flow.map { state -> state.privateTabs.isNotEmpty() }
+ .distinctUntilChanged()
+ .collect { hasPrivateTabs ->
+ if (hasPrivateTabs) {
+ SessionNotificationService.start(applicationContext, permissionRequestHandler)
+ } else {
+ SessionNotificationService.stop(applicationContext)
+ }
+ }
+ }
+ }
+
+ override fun stop() {
+ scope?.cancel()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/SessionNotificationService.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/SessionNotificationService.kt
new file mode 100644
index 0000000000..0360d30179
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/SessionNotificationService.kt
@@ -0,0 +1,240 @@
+/* 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.focus.session
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import android.os.RemoteException
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.utils.ThreadUtils
+import mozilla.components.support.utils.ext.stopForegroundCompat
+import org.mozilla.focus.GleanMetrics.Notifications
+import org.mozilla.focus.GleanMetrics.RecentApps
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.utils.IntentUtils
+
+/**
+ * As long as a session is active this service will keep the notification (and our process) alive.
+ */
+class SessionNotificationService : Service() {
+
+ private var shouldSendTaskRemovedTelemetry = true
+
+ private val notificationManager: NotificationManager by lazy {
+ applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ val action = intent.action ?: return START_NOT_STICKY
+
+ when (action) {
+ ACTION_START -> {
+ // Notifications permission is only needed on Android 13 devices and later.
+ val areNotificationsEnabled =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ try {
+ notificationManager.areNotificationsEnabled()
+ } catch (e: RemoteException) {
+ Logger.warn("Failed to check notifications state", e)
+ false
+ }
+ } else {
+ true
+ }
+
+ if (areNotificationsEnabled) {
+ createNotificationChannelIfNeeded()
+ startForeground(NOTIFICATION_ID, buildNotification())
+ } else {
+ permissionHandler?.invoke()
+ }
+ }
+
+ ACTION_ERASE -> {
+ Notifications.notificationTapped.record(NoExtras())
+
+ shouldSendTaskRemovedTelemetry = false
+
+ if (VisibilityLifeCycleCallback.isInBackground(this)) {
+ VisibilityLifeCycleCallback.finishAndRemoveTaskIfInBackground(this)
+ } else {
+ val eraseIntent = Intent(this, MainActivity::class.java)
+
+ eraseIntent.action = MainActivity.ACTION_ERASE
+ eraseIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+ startActivity(eraseIntent)
+ }
+ }
+
+ else -> throw IllegalStateException("Unknown intent: $intent")
+ }
+
+ return START_NOT_STICKY
+ }
+
+ override fun onTaskRemoved(rootIntent: Intent) {
+ // Do not double send telemetry for notification erase event
+ if (shouldSendTaskRemovedTelemetry) {
+ RecentApps.appRemovedFromList.record(NoExtras())
+ }
+
+ components.tabsUseCases.removeAllTabs()
+
+ stopForegroundCompat(true)
+ stopSelf()
+ }
+
+ private fun buildNotification(): Notification {
+ val eraseIntent = createEraseIntent()
+ val contentTitle = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ getString(R.string.notification_erase_title_android_14)
+ } else {
+ getString(R.string.app_name)
+ }
+
+ val contentText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ getString(R.string.notification_erase_text_android_14_1)
+ } else {
+ getString(R.string.notification_erase_text)
+ }
+
+ return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setContentIntent(eraseIntent)
+ .setVisibility(NotificationCompat.VISIBILITY_SECRET)
+ .setShowWhen(false)
+ .setLocalOnly(true)
+ .setColor(ContextCompat.getColor(this, R.color.accentBright))
+ .addAction(
+ NotificationCompat.Action(
+ R.drawable.ic_notification,
+ getString(R.string.notification_action_open),
+ createOpenActionIntent(),
+ ),
+ )
+ .addAction(
+ NotificationCompat.Action(
+ R.drawable.mozac_ic_delete_24,
+ getString(R.string.notification_action_erase_and_open),
+ createOpenAndEraseActionIntent(),
+ ),
+ )
+ .apply {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ setDeleteIntent(eraseIntent)
+ }
+ }
+ .build()
+ }
+
+ private fun createEraseIntent(): PendingIntent {
+ val notificationIntentFlags =
+ IntentUtils.defaultIntentPendingFlags() or PendingIntent.FLAG_ONE_SHOT
+ val intent = Intent(this, SessionNotificationService::class.java)
+ intent.action = ACTION_ERASE
+
+ return PendingIntent.getService(this, 0, intent, notificationIntentFlags)
+ }
+
+ private fun createOpenActionIntent(): PendingIntent {
+ val openActionIntentFlags =
+ IntentUtils.defaultIntentPendingFlags() or PendingIntent.FLAG_UPDATE_CURRENT
+ val intent = Intent(this, MainActivity::class.java)
+ intent.action = MainActivity.ACTION_OPEN
+
+ return PendingIntent.getActivity(this, 1, intent, openActionIntentFlags)
+ }
+
+ private fun createOpenAndEraseActionIntent(): PendingIntent {
+ val openAndEraseActionIntentFlags =
+ IntentUtils.defaultIntentPendingFlags() or PendingIntent.FLAG_UPDATE_CURRENT
+ val intent = Intent(this, MainActivity::class.java)
+
+ intent.action = MainActivity.ACTION_ERASE
+ intent.putExtra(MainActivity.EXTRA_NOTIFICATION, true)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+
+ return PendingIntent.getActivity(this, 2, intent, openAndEraseActionIntentFlags)
+ }
+
+ private fun createNotificationChannelIfNeeded() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ // Notification channels are only available on Android O or higher.
+ return
+ }
+
+ val notificationChannelName = getString(R.string.notification_browsing_session_channel_name)
+ val notificationChannelDescription = getString(
+ R.string.notification_browsing_session_channel_description,
+ getString(R.string.app_name),
+ )
+
+ val channel = NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ notificationChannelName,
+ NotificationManager.IMPORTANCE_MIN,
+ )
+ channel.importance = NotificationManager.IMPORTANCE_LOW
+ channel.description = notificationChannelDescription
+ channel.enableLights(false)
+ channel.enableVibration(false)
+ channel.setShowBadge(false)
+
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ return null
+ }
+
+ companion object {
+ private var permissionHandler: (() -> Unit)? = null
+ private const val NOTIFICATION_ID = 83
+ private const val NOTIFICATION_CHANNEL_ID = "browsing-session"
+
+ private const val ACTION_START = "start"
+ private const val ACTION_ERASE = "erase"
+
+ internal fun start(context: Context, permissionHandler: (() -> Unit)) {
+ val intent = Intent(context, SessionNotificationService::class.java)
+ intent.action = ACTION_START
+ this.permissionHandler = permissionHandler
+
+ // For #2901: The application is crashing due to the service not calling `startForeground`
+ // before it times out. so this is a speculative fix to decrease the time between these two
+ // calls by running this after potentially expensive calls in FocusApplication.onCreate and
+ // BrowserFragment.inflateView by posting it to the end of the main thread.
+ ThreadUtils.postToMainThread {
+ context.startService(intent)
+ }
+ }
+
+ internal fun stop(context: Context) {
+ val intent = Intent(context, SessionNotificationService::class.java)
+
+ // We want to make sure we always call stop after start. So we're
+ // putting these actions on the same sequential run queue.
+ ThreadUtils.postToMainThread {
+ context.stopService(intent)
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/VisibilityLifeCycleCallback.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/VisibilityLifeCycleCallback.kt
new file mode 100644
index 0000000000..272eeaef55
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/VisibilityLifeCycleCallback.kt
@@ -0,0 +1,81 @@
+/* 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.focus.session
+
+import android.app.Activity
+import android.app.ActivityManager
+import android.app.Application.ActivityLifecycleCallbacks
+import android.content.ComponentCallbacks2
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import org.mozilla.focus.FocusApplication
+import org.mozilla.focus.appreview.AppReviewUtils.Companion.addAppOpenings
+
+/**
+ * This ActivityLifecycleCallbacks implementations tracks if there is at least one activity in the
+ * STARTED state (meaning some part of our application is visible).
+ * Based on this information the current task can be removed if the app is not visible.
+ */
+@Suppress("TooManyFunctions", "EmptyFunctionBlock")
+class VisibilityLifeCycleCallback(private val context: Context) : ActivityLifecycleCallbacks, ComponentCallbacks2 {
+ /**
+ * Activities are not stopped/started in an ordered way. So we are using
+ */
+ private var activitiesInStartedState = 0
+ private var appInForeground = false
+ private fun finishAndRemoveTaskIfInBackground() {
+ if (activitiesInStartedState == 0) {
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ for (task in activityManager.appTasks) {
+ task.finishAndRemoveTask()
+ }
+ }
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ activitiesInStartedState++
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ activitiesInStartedState--
+ }
+
+ override fun onActivityResumed(activity: Activity) {}
+ override fun onActivityPaused(activity: Activity) {}
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ if (!appInForeground) {
+ appInForeground = true
+ addAppOpenings(context)
+ }
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
+ override fun onActivityDestroyed(activity: Activity) {}
+ override fun onTrimMemory(level: Int) {
+ if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
+ appInForeground = false
+ }
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {}
+ override fun onLowMemory() {}
+
+ companion object {
+ /**
+ * If all activities of this app are in the background then finish and remove all tasks. After
+ * that the app won't show up in "recent apps" anymore.
+ */
+ fun finishAndRemoveTaskIfInBackground(context: Context) {
+ (context.applicationContext as FocusApplication)
+ .visibilityLifeCycleCallback
+ ?.finishAndRemoveTaskIfInBackground()
+ }
+
+ fun isInBackground(context: Context): Boolean {
+ return (context.applicationContext as FocusApplication)
+ .visibilityLifeCycleCallback?.activitiesInStartedState == 0
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabViewHolder.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabViewHolder.kt
new file mode 100644
index 0000000000..250c74d716
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabViewHolder.kt
@@ -0,0 +1,51 @@
+/* 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.focus.session.ui
+
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.browser.state.state.TabSessionState
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.ItemSessionBinding
+import org.mozilla.focus.ext.beautifyUrl
+import java.lang.ref.WeakReference
+
+class TabViewHolder(
+ private val binding: ItemSessionBinding,
+) : RecyclerView.ViewHolder(binding.root) {
+
+ private var tabReference: WeakReference = WeakReference(null)
+
+ fun bind(
+ tab: TabSessionState,
+ isCurrentSession: Boolean,
+ selectSession: (TabSessionState) -> Unit,
+ closeSession: (TabSessionState) -> Unit,
+ ) {
+ tabReference = WeakReference(tab)
+
+ val drawable = if (isCurrentSession) {
+ R.drawable.background_list_item_current_session
+ } else {
+ R.drawable.background_list_item_session
+ }
+
+ val title = tab.content.title.ifEmpty { tab.content.url.beautifyUrl() }
+
+ binding.sessionItem.setBackgroundResource(drawable)
+ binding.sessionTitle.apply {
+ setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_link, 0, 0, 0)
+ text = title
+ setOnClickListener {
+ val clickedTab = tabReference.get() ?: return@setOnClickListener
+ selectSession(clickedTab)
+ }
+ }
+
+ binding.closeButton.setOnClickListener {
+ val clickedTab = tabReference.get() ?: return@setOnClickListener
+ closeSession(clickedTab)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsAdapter.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsAdapter.kt
new file mode 100644
index 0000000000..425ce4266c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsAdapter.kt
@@ -0,0 +1,40 @@
+/* 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.focus.session.ui
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import mozilla.components.browser.state.state.TabSessionState
+import org.mozilla.focus.databinding.ItemSessionBinding
+
+/**
+ * Adapter implementation to show a list of active tabs.
+ */
+class TabsAdapter internal constructor(
+ private val tabList: List,
+ private val isCurrentSession: (TabSessionState) -> Boolean,
+ private val selectSession: (TabSessionState) -> Unit,
+ private val closeSession: (TabSessionState) -> Unit,
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabViewHolder {
+ val binding =
+ ItemSessionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return TabViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
+ val currentItem = tabList[position]
+ holder.bind(
+ currentItem,
+ isCurrentSession = isCurrentSession.invoke(currentItem),
+ selectSession = selectSession,
+ closeSession = closeSession,
+ )
+ }
+
+ override fun getItemCount() = tabList.size
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsPopup.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsPopup.kt
new file mode 100644
index 0000000000..044da53ea6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/session/ui/TabsPopup.kt
@@ -0,0 +1,78 @@
+/* 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.focus.session.ui
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.PopupWindow
+import androidx.recyclerview.widget.LinearLayoutManager
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.state.TabSessionState
+import org.mozilla.focus.Components
+import org.mozilla.focus.GleanMetrics.TabCount
+import org.mozilla.focus.databinding.PopupTabsBinding
+import org.mozilla.focus.state.AppAction
+
+class TabsPopup(
+ private val parentView: ViewGroup,
+ private val components: Components,
+) : PopupWindow() {
+ private lateinit var binding: PopupTabsBinding
+
+ init {
+ initializeLayout()
+ }
+
+ private fun initializeLayout() {
+ val layoutInflater =
+ parentView.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ binding = PopupTabsBinding.inflate(layoutInflater, parentView, false)
+ val sessionsAdapter = TabsAdapter(
+ tabList = components.store.state.privateTabs,
+ isCurrentSession = { tab -> isCurrentSession(tab) },
+ selectSession = { tab -> selectSession(tab) },
+ closeSession = { tab -> closeSession(tab) },
+ )
+
+ binding.sessions.apply {
+ adapter = sessionsAdapter
+ layoutManager = LinearLayoutManager(parentView.context)
+ setHasFixedSize(true)
+ }
+ contentView = binding.root
+ isFocusable = true
+ width = FrameLayout.LayoutParams.WRAP_CONTENT
+ height = FrameLayout.LayoutParams.WRAP_CONTENT
+ animationStyle = android.R.style.Animation_Dialog
+ binding.root.setOnClickListener { dismiss() }
+ }
+
+ override fun dismiss() {
+ super.dismiss()
+ val openedTabs = components.store.state.tabs.size
+ TabCount.sessionListClosed.record(TabCount.SessionListClosedExtra(openedTabs))
+
+ components.appStore.dispatch(AppAction.HideTabs)
+ }
+
+ private fun isCurrentSession(tab: TabSessionState): Boolean {
+ return components.store.state.selectedTabId == tab.id
+ }
+
+ private fun selectSession(tab: TabSessionState) {
+ components.tabsUseCases.selectTab.invoke(tab.id)
+ val openedTabs = components.store.state.tabs.size
+ TabCount.sessionListItemTapped.record(TabCount.SessionListItemTappedExtra(openedTabs))
+ dismiss()
+ }
+
+ private fun closeSession(tab: TabSessionState) {
+ components.tabsUseCases.removeTab.invoke(tab.id, selectParentIfExists = false)
+ val openedTabs = components.store.state.tabs.size
+ TabCount.sessionListItemTapped.record(TabCount.SessionListItemTappedExtra(openedTabs))
+ dismiss()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/AboutLibrariesFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/AboutLibrariesFragment.kt
new file mode 100644
index 0000000000..d400ac4855
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/AboutLibrariesFragment.kt
@@ -0,0 +1,27 @@
+/* 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.focus.settings
+
+import mozilla.components.support.license.LibrariesListFragment
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.showToolbar
+
+/**
+ * Displays the list of software licenses used within the app and it's full license content.
+ */
+class AboutLibrariesFragment : LibrariesListFragment() {
+ override val licenseData = LicenseData(
+ licenses = R.raw.third_party_licenses,
+ metadata = R.raw.third_party_license_metadata,
+ )
+
+ override fun onResume() {
+ super.onResume()
+
+ val appName = getString(R.string.app_name)
+ val pageTitle = getString(R.string.open_source_licenses_title, appName)
+ showToolbar(pageTitle)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseComposeFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseComposeFragment.kt
new file mode 100644
index 0000000000..a0e9217398
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseComposeFragment.kt
@@ -0,0 +1,130 @@
+/* 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/. */
+
+@file:Suppress("UnusedMaterialScaffoldPaddingParameter")
+
+package org.mozilla.focus.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.fragment.app.Fragment
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.ext.hideToolbar
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+
+/**
+ * Fragment acting as a wrapper over a [Composable] which will be shown below a [TopAppBar].
+ *
+ * Useful for Fragments shown in otherwise fullscreen Activities such that they would be shown
+ * beneath the status bar not below it.
+ *
+ * Classes extending this are expected to provide the [Composable] content and a basic behavior
+ * for the toolbar: title and navigate up callback.
+ */
+abstract class BaseComposeFragment : Fragment() {
+ /**
+ * Screen title shown in toolbar.
+ */
+ open val titleRes: Int? = null
+
+ open val titleText: String? = null
+
+ /**
+ * Callback for the up navigation button shown in toolbar.
+ */
+ abstract fun onNavigateUp(): () -> Unit
+
+ /**
+ * content of the screen in compose. That will be shown below Toolbar
+ */
+ @Composable
+ abstract fun Content()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ hideToolbar()
+ (requireActivity() as? MainActivity)?.hideStatusBarBackground()
+
+ return ComposeView(requireContext()).apply {
+ setContent {
+ val title = getTitle()
+ FocusTheme {
+ Scaffold(
+ modifier = Modifier
+ .background(colorResource(id = R.color.settings_background))
+ .statusBarsPadding(),
+ ) { paddingValues ->
+ Column {
+ TopAppBar(
+ title = title,
+ modifier = Modifier.padding(paddingValues),
+ onNavigateUpClick = onNavigateUp(),
+ )
+ this@BaseComposeFragment.Content()
+ }
+ }
+ }
+ }
+ isTransitionGroup = true
+ }
+ }
+
+ private fun getTitle(): String {
+ var title = ""
+ titleRes?.let { title = getString(it) }
+ titleText?.let { title = it }
+ return title
+ }
+}
+
+@Composable
+private fun TopAppBar(
+ title: String,
+ modifier: Modifier = Modifier,
+ onNavigateUpClick: () -> Unit,
+) {
+ TopAppBar(
+ title = {
+ Text(
+ text = title,
+ color = focusColors.toolbarColor,
+ )
+ },
+ modifier = modifier,
+ navigationIcon = {
+ IconButton(
+ onClick = onNavigateUpClick,
+ ) {
+ Icon(
+ painterResource(id = R.drawable.mozac_ic_back_24),
+ stringResource(R.string.go_back),
+ tint = focusColors.toolbarColor,
+ )
+ }
+ },
+ backgroundColor = colorResource(R.color.settings_background),
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsFragment.kt
new file mode 100644
index 0000000000..9c6aeea8d9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsFragment.kt
@@ -0,0 +1,35 @@
+/* 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.focus.settings
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import androidx.core.view.MenuHost
+import androidx.core.view.MenuProvider
+import androidx.lifecycle.Lifecycle
+import androidx.preference.PreferenceFragmentCompat
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+
+abstract class BaseSettingsFragment : PreferenceFragmentCompat(), MenuProvider {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val menuHost: MenuHost = requireHost() as MenuHost
+ menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
+
+ // Customize status bar background if the parent activity can be casted to MainActivity
+ (requireActivity() as? MainActivity)?.customizeStatusBar(R.color.settings_background)
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ // no-op
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = false
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsLikeFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsLikeFragment.kt
new file mode 100644
index 0000000000..ada19e0c5e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/BaseSettingsLikeFragment.kt
@@ -0,0 +1,40 @@
+/* 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.focus.settings
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import androidx.core.view.MenuHost
+import androidx.core.view.MenuProvider
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.preference.PreferenceFragmentCompat
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.MainActivity
+
+/**
+ * Similar behavior as [BaseSettingsFragment], but doesn't extend [PreferenceFragmentCompat] and is
+ * a regular [Fragment] instead.
+ */
+open class BaseSettingsLikeFragment : Fragment(), MenuProvider {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val menuHost: MenuHost = requireHost() as MenuHost
+ menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
+
+ // Customize status bar background if the parent activity can be casted to MainActivity
+ (requireActivity() as? MainActivity)?.customizeStatusBar(R.color.settings_background)
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ // no-op
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean = false
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/GeneralSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/GeneralSettingsFragment.kt
new file mode 100644
index 0000000000..a87a6cc8f2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/GeneralSettingsFragment.kt
@@ -0,0 +1,141 @@
+/* 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.focus.settings
+
+import android.content.SharedPreferences
+import android.os.Build
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.requirePreference
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.locale.screen.LanguageStorage.Companion.LOCALE_SYSTEM_DEFAULT
+import org.mozilla.focus.locale.screen.LocaleDescriptor
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.widget.DefaultBrowserPreference
+
+@Suppress("TooManyFunctions") // code is split into multiple functions with their own purpose.
+class GeneralSettingsFragment :
+ BaseSettingsFragment() {
+
+ private lateinit var radioLightTheme: RadioButtonPreference
+ private lateinit var radioDarkTheme: RadioButtonPreference
+ private lateinit var radioDefaultTheme: RadioButtonPreference
+
+ private lateinit var defaultBrowserPreference: DefaultBrowserPreference
+
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.general_settings)
+ setupPreferences()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ defaultBrowserPreference.update()
+ showToolbar(getString(R.string.preference_category_general))
+ }
+
+ private fun setupPreferences() {
+ setupDefaultBrowserPreference()
+ bindLocalePreference()
+ bindLightTheme()
+ bindDarkTheme()
+ bindDefaultTheme()
+ setupRadioGroups()
+ }
+
+ private fun bindLocalePreference() {
+ val localePreference: Preference = requirePreference(R.string.pref_key_locale)
+ localePreference.summary = getLocaleSummary()
+ localePreference.setOnPreferenceClickListener {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.Locale),
+ )
+ true
+ }
+ }
+
+ private fun setupDefaultBrowserPreference() {
+ defaultBrowserPreference = requirePreference(R.string.pref_key_default_browser)
+ }
+
+ private fun bindLightTheme() {
+ radioLightTheme = requirePreference(R.string.pref_key_light_theme)
+ radioLightTheme.onClickListener {
+ setNewTheme(AppCompatDelegate.MODE_NIGHT_NO)
+ }
+ }
+
+ private fun bindDarkTheme() {
+ radioDarkTheme = requirePreference(R.string.pref_key_dark_theme)
+ radioDarkTheme.onClickListener {
+ setNewTheme(AppCompatDelegate.MODE_NIGHT_YES)
+ }
+ }
+
+ private fun bindDefaultTheme() {
+ radioDefaultTheme = requirePreference(R.string.pref_key_default_theme)
+ val defaultThemeTitle = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ context?.getString(R.string.preference_follow_device_theme)
+ } else {
+ context?.getString(R.string.preference_auto_battery_theme)
+ }
+
+ radioDefaultTheme.apply {
+ title = defaultThemeTitle
+ onClickListener {
+ setDefaultTheme()
+ }
+ }
+ }
+
+ private fun getLocaleSummary(): CharSequence? {
+ val sharedConfig: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ val value: String? =
+ sharedConfig.getString(
+ resources.getString(R.string.pref_key_locale),
+ resources.getString(R.string.preference_language_systemdefault),
+ )
+ return value?.let {
+ if (value.isEmpty() || value == LOCALE_SYSTEM_DEFAULT) {
+ return resources.getString(R.string.preference_language_systemdefault)
+ }
+ LocaleDescriptor(it).getNativeName()
+ }
+ }
+
+ private fun setupRadioGroups() {
+ addToRadioGroup(
+ radioLightTheme,
+ radioDarkTheme,
+ radioDefaultTheme,
+ )
+ }
+
+ private fun setNewTheme(mode: Int) {
+ if (AppCompatDelegate.getDefaultNightMode() == mode) return
+ AppCompatDelegate.setDefaultNightMode(mode)
+ activity?.recreate()
+
+ requireComponents.engine.settings.preferredColorScheme = requireComponents.settings.getPreferredColorScheme()
+ requireComponents.sessionUseCases.reload.invoke()
+ }
+
+ private fun setDefaultTheme() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ AppCompatDelegate.setDefaultNightMode(
+ AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
+ )
+ } else {
+ AppCompatDelegate.setDefaultNightMode(
+ AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY,
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/HttpsOnlyModePreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/HttpsOnlyModePreference.kt
new file mode 100644
index 0000000000..6c6dcbd895
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/HttpsOnlyModePreference.kt
@@ -0,0 +1,35 @@
+/* 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.focus.settings
+
+import android.content.Context
+import android.util.AttributeSet
+import mozilla.components.concept.engine.Engine
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * Preference for HTTPS-Only mode.
+ */
+class HttpsOnlyModePreference(
+ context: Context,
+ attrs: AttributeSet?,
+) : LearnMoreSwitchPreference(context, attrs) {
+
+ override fun getLearnMoreUrl() =
+ SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.HTTPS_ONLY)
+
+ init {
+ setOnPreferenceChangeListener { _, newValue ->
+ val enableHttpsOnly = newValue as Boolean
+ context.components.engine.settings.httpsOnlyMode = if (enableHttpsOnly) {
+ Engine.HttpsOnlyMode.ENABLED
+ } else {
+ Engine.HttpsOnlyMode.DISABLED
+ }
+ true
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/InstalledSearchEnginesSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/InstalledSearchEnginesSettingsFragment.kt
new file mode 100644
index 0000000000..0a047ea63c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/InstalledSearchEnginesSettingsFragment.kt
@@ -0,0 +1,138 @@
+/* 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.focus.settings
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import androidx.preference.Preference
+import mozilla.components.browser.state.state.SearchState
+import mozilla.components.browser.state.state.availableSearchEngines
+import mozilla.components.browser.state.state.searchEngines
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.search.SearchUseCases
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.SearchEngines
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.search.RadioSearchEngineListPreference
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import kotlin.collections.forEach as withEach
+
+class InstalledSearchEnginesSettingsFragment : BaseSettingsFragment() {
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ //
+ }
+
+ companion object {
+ fun newInstance() = InstalledSearchEnginesSettingsFragment()
+ var languageChanged: Boolean = false
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.preference_choose_search_engine))
+
+ if (languageChanged) {
+ restoreSearchEngines()
+ } else {
+ refetchSearchEngines()
+ }
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ super.onCreateMenu(menu, menuInflater)
+ menuInflater.inflate(R.menu.menu_search_engines, menu)
+ }
+
+ override fun onPrepareMenu(menu: Menu) {
+ super.onPrepareMenu(menu)
+ menu.findItem(R.id.menu_restore_default_engines)?.let {
+ it.isEnabled = !requireComponents.store.state.search.hasDefaultSearchEnginesOnly()
+ }
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ val currentEnginesCount = requireComponents.store.state.search.searchEngines.size
+
+ return when (menuItem.itemId) {
+ R.id.menu_remove_search_engines -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.SearchRemove),
+ )
+ SearchEngines.openRemoveScreen.record(
+ SearchEngines.OpenRemoveScreenExtra(currentEnginesCount),
+ )
+
+ true
+ }
+ R.id.menu_restore_default_engines -> {
+ restoreSearchEngines()
+ SearchEngines.restoreDefaultEngines.record(
+ SearchEngines.RestoreDefaultEnginesExtra(currentEnginesCount),
+ )
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun restoreSearchEngines() {
+ restoreSearchDefaults(requireComponents.store, requireComponents.searchUseCases)
+ refetchSearchEngines()
+ languageChanged = false
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ return when (preference.key) {
+ resources.getString(R.string.pref_key_manual_add_search_engine) -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SearchAdd),
+ )
+ SearchEngines.addEngineTapped.record(NoExtras())
+
+ return true
+ }
+ else -> {
+ super.onPreferenceTreeClick(preference)
+ }
+ }
+ }
+
+ /**
+ * Refresh search engines list.
+ */
+ private fun refetchSearchEngines() {
+ // Refresh this preference screen to display changes.
+ preferenceScreen?.removeAll()
+ addPreferencesFromResource(R.xml.search_engine_settings)
+
+ val pref: RadioSearchEngineListPreference? = preferenceScreen.findPreference(
+ resources.getString(R.string.pref_key_radio_search_engine_list),
+ )
+ pref?.refetchSearchEngines()
+ }
+}
+
+private fun SearchState.hasDefaultSearchEnginesOnly(): Boolean {
+ return availableSearchEngines.isEmpty() && additionalSearchEngines.isEmpty() && customSearchEngines.isEmpty()
+}
+
+private fun restoreSearchDefaults(store: BrowserStore, useCases: SearchUseCases) {
+ store.state.search.customSearchEngines.withEach { searchEngine ->
+ useCases.removeSearchEngine(
+ searchEngine,
+ )
+ }
+ store.state.search.hiddenSearchEngines.withEach { searchEngine ->
+ useCases.addSearchEngine(
+ searchEngine,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/LearnMoreSwitchPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/LearnMoreSwitchPreference.kt
new file mode 100644
index 0000000000..9da6eaf2b2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/LearnMoreSwitchPreference.kt
@@ -0,0 +1,58 @@
+/* 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.focus.settings
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.preference.PreferenceViewHolder
+import androidx.preference.SwitchPreferenceCompat
+import mozilla.components.browser.state.state.SessionState
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.state.AppAction
+
+abstract class LearnMoreSwitchPreference(context: Context, attrs: AttributeSet?) :
+ SwitchPreferenceCompat(context, attrs) {
+
+ init {
+ layoutResource = R.layout.preference_switch_learn_more
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ getDescription()?.let {
+ val summaryView = holder.findViewById(android.R.id.summary) as TextView
+ summaryView.text = it
+ summaryView.isVisible = true
+ }
+
+ val learnMoreLink = holder.findViewById(R.id.link) as TextView
+ learnMoreLink.setOnClickListener {
+ val tabId = context.components.tabsUseCases.addTab(
+ getLearnMoreUrl(),
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+
+ context.components.appStore.dispatch(
+ AppAction.OpenTab(tabId),
+ )
+ }
+
+ val backgroundDrawableArray =
+ context.obtainStyledAttributes(intArrayOf(R.attr.selectableItemBackground))
+ val backgroundDrawable = backgroundDrawableArray.getDrawable(0)
+ backgroundDrawableArray.recycle()
+ learnMoreLink.background = backgroundDrawable
+ }
+
+ open fun getDescription(): String? = null
+
+ abstract fun getLearnMoreUrl(): String
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/ManualAddSearchEngineSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/ManualAddSearchEngineSettingsFragment.kt
new file mode 100644
index 0000000000..8fff732ef0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/ManualAddSearchEngineSettingsFragment.kt
@@ -0,0 +1,262 @@
+/* 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.focus.settings
+
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import androidx.annotation.WorkerThread
+import androidx.core.view.forEach
+import com.google.android.material.snackbar.Snackbar
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import mozilla.components.browser.icons.IconRequest
+import mozilla.components.browser.state.state.searchEngines
+import mozilla.components.concept.fetch.Client
+import mozilla.components.concept.fetch.Request
+import mozilla.components.concept.fetch.Request.Redirect.FOLLOW
+import mozilla.components.feature.search.ext.createSearchEngine
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.ktx.util.URLStringUtils
+import org.mozilla.focus.GleanMetrics.SearchEngines
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.search.ManualAddSearchEnginePreference
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.utils.SupportUtils
+import org.mozilla.focus.utils.ViewUtils
+import java.io.IOException
+import java.net.MalformedURLException
+import java.net.URL
+import java.util.concurrent.TimeUnit
+
+@Suppress("TooManyFunctions")
+class ManualAddSearchEngineSettingsFragment : BaseSettingsFragment() {
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.manual_add_search_engine)
+ }
+
+ private var scope: CoroutineScope? = null
+ private var menuItemForActiveAsyncTask: MenuItem? = null
+ private var job: Job? = null
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.action_option_add_search_engine))
+ }
+
+ override fun onPause() {
+ super.onPause()
+ setUiIsValidatingAsync(false, menuItemForActiveAsyncTask)
+ menuItemForActiveAsyncTask = null
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ menuInflater.inflate(R.menu.menu_search_engine_manual_add, menu)
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ val openLearnMore = {
+ val learnMoreUrl = SupportUtils.getSumoURLForTopic(
+ SupportUtils.getAppVersion(requireContext()),
+ SupportUtils.SumoTopic.ADD_SEARCH_ENGINE,
+ )
+ SupportUtils.openUrlInCustomTab(requireActivity(), learnMoreUrl)
+ SearchEngines.learnMoreTapped.record(NoExtras())
+
+ true
+ }
+
+ val saveSearchEngine = {
+ val engineName = requireView().findViewById(R.id.edit_engine_name).text.toString()
+ val searchQuery = requireView().findViewById(R.id.edit_search_string).text.toString()
+
+ val pref = findManualAddSearchEnginePreference(R.string.pref_key_manual_add_search_engine)
+
+ val existingEngines = requireContext().components.store.state.search.searchEngines
+ val engineValid = pref?.validateEngineNameAndShowError(engineName, existingEngines) ?: false
+ val searchValid = pref?.validateSearchQueryAndShowError(searchQuery) ?: false
+ val isPartialSuccess = engineValid && searchValid
+
+ if (isPartialSuccess) {
+ ViewUtils.hideKeyboard(view)
+ setUiIsValidatingAsync(true, menuItem)
+
+ menuItemForActiveAsyncTask = menuItem
+ scope?.launch {
+ validateSearchEngine(engineName, searchQuery, requireComponents.client)
+ }
+ } else {
+ SearchEngines.saveEngineTapped.record(SearchEngines.SaveEngineTappedExtra(false))
+ }
+
+ true
+ }
+
+ return when (menuItem.itemId) {
+ R.id.learn_more -> openLearnMore()
+ R.id.menu_save_search_engine -> saveSearchEngine()
+ else -> false
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ scope = CoroutineScope(Dispatchers.IO)
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+
+ override fun onDestroyView() {
+ scope?.cancel()
+ super.onDestroyView()
+ if (view != null) ViewUtils.hideKeyboard(view)
+ }
+
+ private fun setUiIsValidatingAsync(isValidating: Boolean, saveMenuItem: MenuItem?) {
+ val pref = findManualAddSearchEnginePreference(R.string.pref_key_manual_add_search_engine)
+ val updateViews = {
+ // Disable text entry until done validating
+ val viewGroup = view as ViewGroup
+ enableAllSubviews(!isValidating, viewGroup)
+
+ saveMenuItem?.isEnabled = !isValidating
+ }
+
+ if (isValidating) {
+ view?.alpha = DISABLED_ALPHA
+ // Delay showing the loading indicator to prevent it flashing on the screen
+ job = scope?.launch(Dispatchers.Main) {
+ delay(LOADING_INDICATOR_DELAY)
+ pref?.setProgressViewShown(isValidating)
+ updateViews()
+ }
+ } else {
+ view?.alpha = 1f
+ job?.cancel()
+ pref?.setProgressViewShown(isValidating)
+ updateViews()
+ }
+ }
+
+ private fun enableAllSubviews(shouldEnable: Boolean, viewGroup: ViewGroup) {
+ viewGroup.forEach { child ->
+ if (child is ViewGroup) {
+ enableAllSubviews(shouldEnable, child)
+ } else {
+ child.isEnabled = shouldEnable
+ }
+ }
+ }
+
+ private fun findManualAddSearchEnginePreference(id: Int): ManualAddSearchEnginePreference? {
+ return findPreference(getString(id)) as? ManualAddSearchEnginePreference
+ }
+
+ companion object {
+ private const val LOGTAG = "ManualAddSearchEngine"
+ private const val SEARCH_QUERY_VALIDATION_TIMEOUT_MILLIS = 4000
+ private const val VALID_RESPONSE_CODE_UPPER_BOUND = 300
+ private const val DISABLED_ALPHA = 0.5f
+ private const val LOADING_INDICATOR_DELAY: Long = 1000
+
+ @WorkerThread
+ @JvmStatic
+ fun isValidSearchQueryURL(client: Client, query: String): Boolean {
+ // we should share the code to substitute and normalize the search string (see SearchEngine.buildSearchUrl).
+ val encodedTestQuery = Uri.encode("testSearchEngineValidation")
+
+ val normalizedHttpsSearchURLStr = URLStringUtils.toNormalizedURL(query)
+ val searchURLStr = normalizedHttpsSearchURLStr.replace("%s".toRegex(), encodedTestQuery)
+
+ try { URL(searchURLStr) } catch (e: MalformedURLException) {
+ // Don't log exception to avoid leaking URL.
+ Log.d(LOGTAG, "Failure to get response code from server: returning invalid search query")
+ return false
+ }
+
+ val request = Request(
+ url = searchURLStr,
+ connectTimeout = SEARCH_QUERY_VALIDATION_TIMEOUT_MILLIS.toLong() to TimeUnit.MILLISECONDS,
+ readTimeout = SEARCH_QUERY_VALIDATION_TIMEOUT_MILLIS.toLong() to TimeUnit.MILLISECONDS,
+ redirect = FOLLOW,
+ private = true,
+ )
+
+ return try {
+ val response = client.fetch(request)
+ // Close the response stream to ensure the body is closed correctly. See https://bugzilla.mozilla.org/show_bug.cgi?id=1603114.
+ response.close()
+
+ response.status < VALID_RESPONSE_CODE_UPPER_BOUND
+ } catch (e: IOException) {
+ Log.d(LOGTAG, "Failure to get response code from server: returning invalid search query")
+ false
+ }
+ }
+ }
+
+ private suspend fun validateSearchEngine(engineName: String, query: String, client: Client) {
+ val isValidSearchQuery = isValidSearchQueryURL(client, query)
+
+ withContext(Dispatchers.Main) {
+ if (!isActive) {
+ return@withContext
+ }
+
+ if (isValidSearchQuery) {
+ requireComponents.searchUseCases.addSearchEngine(
+ createSearchEngine(
+ engineName,
+ query.toSearchUrl(),
+ requireComponents.icons.loadIcon(IconRequest(query, isPrivate = true)).await().bitmap,
+ ),
+ )
+
+ ViewUtils.showBrandedSnackbar(requireView(), R.string.search_add_confirmation, Snackbar.LENGTH_SHORT)
+ requireActivity().settings.setDefaultSearchEngineByName(engineName)
+ SearchEngines.saveEngineTapped.record(SearchEngines.SaveEngineTappedExtra(true))
+
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(requireComponents.store.state.selectedTabId),
+ )
+ } else {
+ showServerError()
+ SearchEngines.saveEngineTapped.record(SearchEngines.SaveEngineTappedExtra(false))
+ }
+
+ setUiIsValidatingAsync(false, menuItemForActiveAsyncTask)
+ menuItemForActiveAsyncTask = null
+ }
+ }
+
+ private fun showServerError() {
+ val pref = findManualAddSearchEnginePreference(R.string.pref_key_manual_add_search_engine)
+ pref?.setSearchQueryErrorText(getString(R.string.error_hostLookup_title))
+ }
+}
+
+private fun String.toSearchUrl(): String {
+ return replace("%s", "{searchTerms}")
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/MozillaSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/MozillaSettingsFragment.kt
new file mode 100644
index 0000000000..ddc0febdb7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/MozillaSettingsFragment.kt
@@ -0,0 +1,97 @@
+/* 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.focus.settings
+
+import android.os.Bundle
+import androidx.preference.Preference
+import mozilla.components.browser.state.state.SessionState
+import org.mozilla.focus.R
+import org.mozilla.focus.browser.LocalizedContent
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.utils.AppConstants
+import org.mozilla.focus.utils.SupportUtils
+
+class MozillaSettingsFragment :
+ BaseSettingsFragment() {
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.mozilla_settings)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ showToolbar(getString(R.string.preference_category_mozilla))
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ // AppCompatActivity has a Toolbar that is used as the ActionBar, and it conflicts with the ActionBar
+ // used by PreferenceScreen to create the headers (with title, back navigation), so we wrap all these
+ // "preference screens" into separate activities.
+ val activity = activity ?: return super.onPreferenceTreeClick(preference)
+
+ when (preference.key) {
+ resources.getString(R.string.pref_key_about) -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.About),
+ )
+ }
+ resources.getString(R.string.pref_key_help) -> run {
+ val tabId = activity.components.tabsUseCases.addTab(
+ SupportUtils.HELP_URL,
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ requireComponents.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+ resources.getString(R.string.pref_key_rights) -> run {
+ val tabId = activity.components.tabsUseCases.addTab(
+ LocalizedContent.URL_RIGHTS,
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ requireComponents.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+ resources.getString(R.string.pref_key_privacy_notice) -> {
+ val url = if (AppConstants.isKlarBuild) {
+ SupportUtils.PRIVACY_NOTICE_KLAR_URL
+ } else {
+ SupportUtils.PRIVACY_NOTICE_URL
+ }
+
+ val tabId = activity.components.tabsUseCases.addTab(
+ url,
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ requireComponents.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+ resources.getString(R.string.pref_key_licensing_info) -> {
+ val tabId = activity.components.tabsUseCases.addTab(
+ "about:license",
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ requireComponents.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+ resources.getString(R.string.pref_key_libraries_we_use) -> {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.Licenses),
+ )
+ }
+ }
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ companion object {
+ fun newInstance(): MozillaSettingsFragment = MozillaSettingsFragment()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RadioButtonPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RadioButtonPreference.kt
new file mode 100644
index 0000000000..14eb33ac48
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RadioButtonPreference.kt
@@ -0,0 +1,191 @@
+/* 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.focus.settings
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.AttributeSet
+import android.widget.RadioButton
+import android.widget.TextView
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.content.res.TypedArrayUtils.getAttr
+import androidx.core.content.withStyledAttributes
+import androidx.core.text.HtmlCompat
+import androidx.core.view.isVisible
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceViewHolder
+import mozilla.components.support.ktx.android.content.res.resolveAttribute
+import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
+import org.mozilla.focus.R
+
+interface GroupableRadioButton {
+ fun updateRadioValue(isChecked: Boolean)
+
+ fun addToRadioGroup(radioButton: GroupableRadioButton)
+}
+
+/**
+ * Connect all the given radio buttons into a group,
+ * so that when one radio is checked the others are unchecked.
+ */
+fun addToRadioGroup(vararg radios: GroupableRadioButton) {
+ for (i in 0..radios.lastIndex) {
+ for (j in (i + 1)..radios.lastIndex) {
+ radios[i].addToRadioGroup(radios[j])
+ radios[j].addToRadioGroup(radios[i])
+ }
+ }
+}
+
+fun Iterable.uncheckAll() {
+ forEach { it.updateRadioValue(isChecked = false) }
+}
+
+@SuppressLint("RestrictedApi")
+open class RadioButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : Preference(context, attrs), GroupableRadioButton {
+ private val radioGroups = mutableListOf()
+ private var summaryView: TextView? = null
+ private var titleView: TextView? = null
+ private var radioButton: RadioButton? = null
+ private var shouldSummaryBeParsedAsHtmlContent: Boolean = true
+ private var defaultValue: Boolean = false
+ private var clickListener: (() -> Unit)? = null
+
+ private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+ val isChecked: Boolean
+ get() = radioButton?.isChecked == true
+
+ init {
+ layoutResource = R.layout.preference_radio_button
+
+ context.withStyledAttributes(
+ attrs,
+ R.styleable.RadioButtonPreference,
+ getAttr(
+ context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle,
+ ),
+ 0,
+ ) {
+ defaultValue = when {
+ hasValue(R.styleable.RadioButtonPreference_defaultValue) ->
+ getBoolean(R.styleable.RadioButtonPreference_defaultValue, false)
+ hasValue(R.styleable.RadioButtonPreference_android_defaultValue) ->
+ getBoolean(R.styleable.RadioButtonPreference_android_defaultValue, false)
+ else -> false
+ }
+ }
+ }
+
+ override fun addToRadioGroup(radioButton: GroupableRadioButton) {
+ radioGroups.add(radioButton)
+ }
+
+ fun onClickListener(listener: (() -> Unit)) {
+ clickListener = listener
+ }
+
+ override fun setEnabled(enabled: Boolean) {
+ super.setEnabled(enabled)
+ if (!enabled) {
+ summaryView?.alpha = HALF_ALPHA
+ titleView?.alpha = HALF_ALPHA
+ } else {
+ summaryView?.alpha = FULL_ALPHA
+ titleView?.alpha = FULL_ALPHA
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ bindRadioButton(holder)
+
+ bindTitle(holder)
+
+ bindSummaryView(holder)
+
+ setOnPreferenceClickListener {
+ updateRadioValue(true)
+
+ toggleRadioGroups()
+ clickListener?.invoke()
+ true
+ }
+ }
+
+ override fun updateRadioValue(isChecked: Boolean) {
+ persistBoolean(isChecked)
+ radioButton?.isChecked = isChecked
+ preferences.edit().putBoolean(key, isChecked)
+ .apply()
+ onPreferenceChangeListener?.onPreferenceChange(this, isChecked)
+ }
+
+ private fun bindRadioButton(holder: PreferenceViewHolder) {
+ radioButton = holder.findViewById(R.id.radio_button) as RadioButton
+ radioButton?.isChecked = preferences.getBoolean(key, defaultValue)
+ radioButton?.setStartCheckedIndicator()
+ }
+
+ private fun toggleRadioGroups() {
+ if (radioButton?.isChecked == true) {
+ radioGroups.uncheckAll()
+ }
+ }
+
+ private fun bindTitle(holder: PreferenceViewHolder) {
+ titleView = holder.findViewById(R.id.title) as TextView
+ titleView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA
+
+ if (!title.isNullOrEmpty()) {
+ titleView?.text = title
+ }
+ }
+
+ private fun bindSummaryView(holder: PreferenceViewHolder) {
+ summaryView = holder.findViewById(R.id.widget_summary) as TextView
+
+ summaryView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA
+ summaryView?.let {
+ if (!summary.isNullOrEmpty()) {
+ it.text = if (shouldSummaryBeParsedAsHtmlContent) {
+ HtmlCompat.fromHtml(summary.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT)
+ } else {
+ summary
+ }
+
+ it.isVisible = true
+ } else {
+ it.isVisible = false
+ }
+ }
+ }
+
+ /**
+ * In devices with Android 6, when we use android:button="@null" android:drawableStart doesn't work via xml
+ * as a result we have to apply it programmatically. More info about this issue https://github.com/mozilla-mobile/fenix/issues/1414
+ */
+ private fun RadioButton.setStartCheckedIndicator() {
+ val attr = context.theme.resolveAttribute(android.R.attr.listChoiceIndicatorSingle)
+ val buttonDrawable = AppCompatResources.getDrawable(context, attr)
+ buttonDrawable?.apply {
+ setBounds(0, 0, intrinsicWidth, intrinsicHeight)
+ }
+ putCompoundDrawablesRelative(start = buttonDrawable)
+ }
+
+ companion object {
+ const val HALF_ALPHA = 0.5F
+ const val FULL_ALPHA = 1F
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RemoveSearchEnginesSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RemoveSearchEnginesSettingsFragment.kt
new file mode 100644
index 0000000000..23fd044564
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/RemoveSearchEnginesSettingsFragment.kt
@@ -0,0 +1,77 @@
+/* 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.focus.settings
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import mozilla.components.browser.state.state.searchEngines
+import org.mozilla.focus.GleanMetrics.SearchEngines
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.search.MultiselectSearchEngineListPreference
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.utils.ViewUtils
+
+class RemoveSearchEnginesSettingsFragment : BaseSettingsFragment() {
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.remove_search_engines)
+ }
+
+ companion object {
+ fun newInstance() = RemoveSearchEnginesSettingsFragment()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.preference_search_remove_title))
+ }
+
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ super.onCreateMenu(menu, menuInflater)
+ menuInflater.inflate(R.menu.menu_remove_search_engines, menu)
+ }
+
+ override fun onPrepareMenu(menu: Menu) {
+ super.onPrepareMenu(menu)
+ view?.post {
+ val pref = preferenceScreen
+ .findPreference(resources.getString(R.string.pref_key_multiselect_search_engine_list))
+ as? MultiselectSearchEngineListPreference
+
+ menu.findItem(R.id.menu_delete_items)?.let {
+ ViewUtils.setMenuItemEnabled(it, pref!!.atLeastOneEngineChecked())
+ }
+ }
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ return when (menuItem.itemId) {
+ R.id.menu_delete_items -> {
+ val pref: MultiselectSearchEngineListPreference? = preferenceScreen
+ .findPreference(resources.getString(R.string.pref_key_multiselect_search_engine_list))
+
+ val enginesToRemove = pref!!.checkedEngineIds
+
+ requireComponents.store.state.search.searchEngines.filter { searchEngine ->
+ searchEngine.id in enginesToRemove
+ }.forEach { searchEngine ->
+ requireComponents.searchUseCases.removeSearchEngine(searchEngine)
+ }
+
+ SearchEngines.removeEngines.record(SearchEngines.RemoveEnginesExtra(enginesToRemove.size))
+
+ requireComponents.appStore.dispatch(
+ AppAction.NavigateUp(requireComponents.store.state.selectedTabId),
+ )
+ true
+ }
+ else -> false
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SafeBrowsingSwitchPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SafeBrowsingSwitchPreference.kt
new file mode 100644
index 0000000000..2136212be3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SafeBrowsingSwitchPreference.kt
@@ -0,0 +1,19 @@
+/* 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.focus.settings
+
+import android.content.Context
+import android.util.AttributeSet
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * Switch preference for enabling/disabling autocompletion for custom domains entered by the user.
+ */
+class SafeBrowsingSwitchPreference(
+ context: Context,
+ attrs: AttributeSet?,
+) : LearnMoreSwitchPreference(context, attrs) {
+ override fun getLearnMoreUrl() =
+ SupportUtils.getSafeBrowsingURL()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SearchSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SearchSettingsFragment.kt
new file mode 100644
index 0000000000..ab6308f5cb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SearchSettingsFragment.kt
@@ -0,0 +1,70 @@
+/* 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.focus.settings
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.preference.Preference
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.SearchEngines
+import org.mozilla.focus.GleanMetrics.ShowSearchSuggestions
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+
+class SearchSettingsFragment :
+ BaseSettingsFragment(),
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.search_settings)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+
+ showToolbar(getString(R.string.preference_category_search))
+ }
+
+ override fun onPause() {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPause()
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ when (preference.key) {
+ resources.getString(R.string.pref_key_search_engine) -> run {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SearchList),
+ )
+ SearchEngines.openSettings.record(NoExtras())
+ }
+ resources.getString(R.string.pref_key_screen_autocomplete) ->
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SearchAutocomplete),
+ )
+ }
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ when (key) {
+ resources.getString(R.string.pref_key_show_search_suggestions) ->
+ ShowSearchSuggestions.changedFromSettings.record(
+ ShowSearchSuggestions.ChangedFromSettingsExtra(sharedPreferences.getBoolean(key, false)),
+ )
+ }
+ }
+
+ companion object {
+
+ fun newInstance(): SearchSettingsFragment {
+ return SearchSettingsFragment()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsFragment.kt
new file mode 100644
index 0000000000..e5227ccacb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsFragment.kt
@@ -0,0 +1,50 @@
+/* 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.focus.settings
+
+import android.os.Bundle
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+
+class SettingsFragment : BaseSettingsFragment() {
+
+ override fun onCreatePreferences(bundle: Bundle?, s: String?) {
+ addPreferencesFromResource(R.xml.settings)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ showToolbar(getString(R.string.menu_settings))
+ }
+
+ override fun onPreferenceTreeClick(preference: androidx.preference.Preference): Boolean {
+ val resources = resources
+
+ val page = when (preference.key) {
+ resources.getString(R.string.pref_key_general_screen) -> Screen.Settings.Page.General
+ resources.getString(R.string.pref_key_privacy_security_screen) -> Screen.Settings.Page.Privacy
+ resources.getString(R.string.pref_key_search_screen) -> Screen.Settings.Page.Search
+ resources.getString(R.string.pref_key_advanced_screen) -> Screen.Settings.Page.Advanced
+ resources.getString(R.string.pref_key_mozilla_screen) -> Screen.Settings.Page.Mozilla
+ else -> throw IllegalStateException("Unknown preference: ${preference.key}")
+ }
+
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page),
+ )
+
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ companion object {
+ fun newInstance(): SettingsFragment {
+ return SettingsFragment()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/StatePreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/StatePreference.kt
new file mode 100644
index 0000000000..b1ae49b436
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/StatePreference.kt
@@ -0,0 +1,47 @@
+/* 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.focus.settings
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.TextView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.settings
+
+/**
+ * State preference that will show the current state as a summary and a sub screen to configure the behavior.
+ */
+class StatePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
+ private var summaryView: TextView? = null
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ summaryView = holder.findViewById(android.R.id.summary) as TextView
+ setValueByKey(key)
+ }
+
+ private fun setValueByKey(key: String?) {
+ key?.let {
+ val state = when (key) {
+ context.getString(R.string.pref_key_studies) -> context.settings.isExperimentationEnabled
+
+ context.getString(R.string.pref_key_screen_autocomplete) ->
+ context.settings.shouldAutocompleteFromShippedDomainList() ||
+ context.settings.shouldAutocompleteFromCustomDomainList()
+ else -> false
+ }
+ val summaryText = if (state) {
+ R.string.preference_state_on
+ } else {
+ R.string.preference_state_off
+ }
+
+ summaryView?.setText(summaryText)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/AdvancedSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/AdvancedSettingsFragment.kt
new file mode 100644
index 0000000000..c29f49ec4a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/AdvancedSettingsFragment.kt
@@ -0,0 +1,80 @@
+/* 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.focus.settings.advanced
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.preference.Preference
+import org.mozilla.focus.GleanMetrics.AdvancedSettings
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.application
+import org.mozilla.focus.ext.getPreferenceKey
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.utils.AppConstants.isDevBuild
+
+class AdvancedSettingsFragment :
+ BaseSettingsFragment(),
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.advanced_settings)
+ findPreference(getPreferenceKey(R.string.pref_key_secret_settings))?.isVisible =
+ requireComponents.appStore.state.secretSettingsEnabled
+ findPreference(getPreferenceKey(R.string.pref_key_leakcanary))?.isVisible =
+ isDevBuild
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+
+ showToolbar(getString(R.string.preference_category_advanced))
+ }
+
+ override fun onPause() {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPause()
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ when (key) {
+ getString(R.string.pref_key_remote_debugging) -> {
+ requireComponents.engine.settings.remoteDebuggingEnabled =
+ sharedPreferences.getBoolean(key, false)
+
+ AdvancedSettings.remoteDebugSettingChanged.record(
+ AdvancedSettings.RemoteDebugSettingChangedExtra(sharedPreferences.all[key] as Boolean),
+ )
+ }
+
+ getString(R.string.pref_key_open_links_in_external_app) ->
+ AdvancedSettings.openLinksSettingChanged.record(
+ AdvancedSettings.OpenLinksSettingChangedExtra(sharedPreferences.all[key] as Boolean),
+ )
+ getString(R.string.pref_key_leakcanary) -> {
+ context?.application?.updateLeakCanaryState(sharedPreferences.all[key] as Boolean)
+ }
+ }
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ if (preference.key == resources.getString(R.string.pref_key_secret_settings)) {
+ requireComponents.appStore.dispatch(AppAction.OpenSettings(page = Screen.Settings.Page.SecretSettings))
+ }
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ companion object {
+
+ fun newInstance(): AdvancedSettingsFragment {
+ return AdvancedSettingsFragment()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SecretSettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SecretSettingsFragment.kt
new file mode 100644
index 0000000000..4526f56f19
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SecretSettingsFragment.kt
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.focus.settings.advanced
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.preference.SwitchPreferenceCompat
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.getPreferenceKey
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.requirePreference
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsFragment
+import kotlin.system.exitProcess
+
+class SecretSettingsFragment :
+ BaseSettingsFragment(),
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ override fun onStart() {
+ super.onStart()
+ showToolbar(getString(R.string.preference_secret_settings))
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ addPreferencesFromResource(R.xml.secret_settings)
+
+ requirePreference(R.string.pref_key_remote_server_prod).apply {
+ isVisible = true
+ isChecked = context.settings.useProductionRemoteSettingsServer
+ onPreferenceChangeListener = SharedPreferenceUpdater()
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ findPreference(
+ getPreferenceKey(R.string.pref_key_use_nimbus_preview),
+ )?.let { nimbusPreviewPref ->
+ if (key == nimbusPreviewPref.key) {
+ requireComponents.settings.shouldUseNimbusPreview = nimbusPreviewPref.isChecked
+ quitTheApp()
+ }
+ }
+ }
+
+ private fun quitTheApp() {
+ exitProcess(0)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SharedPreferenceUpdater.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SharedPreferenceUpdater.kt
new file mode 100644
index 0000000000..9f3760fbc8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/advanced/SharedPreferenceUpdater.kt
@@ -0,0 +1,24 @@
+/* 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.focus.settings.advanced
+
+import androidx.core.content.edit
+import androidx.preference.Preference
+import org.mozilla.focus.ext.settings
+
+/**
+ * Updates the corresponding [android.content.SharedPreferences] when the boolean [Preference] is changed.
+ * The preference key is used as the shared preference key.
+ */
+open class SharedPreferenceUpdater : Preference.OnPreferenceChangeListener {
+
+ override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
+ val newBooleanValue = newValue as? Boolean ?: return false
+ preference.context.settings.preferences.edit {
+ putBoolean(preference.key, newBooleanValue)
+ }
+ return true
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionOption.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionOption.kt
new file mode 100644
index 0000000000..8860151de6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionOption.kt
@@ -0,0 +1,44 @@
+/* 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.focus.settings.permissions
+
+import org.mozilla.focus.R
+
+sealed class AutoplayOption {
+
+ data class AllowAudioVideo(
+ override val prefKeyId: Int = R.string.pref_key_allow_autoplay_audio_video,
+ override val titleId: Int = R.string.preference_allow_audio_video_autoplay,
+ ) : SitePermissionOption(prefKeyId = prefKeyId, titleId = titleId)
+
+ data class BlockAudioOnly(
+ override val prefKeyId: Int = R.string.pref_key_block_autoplay_audio_only,
+ override val titleId: Int = R.string.preference_block_autoplay_audio_only,
+ override val summaryId: Int = R.string.preference_block_autoplay_audio_only_summary,
+ ) : SitePermissionOption(prefKeyId = prefKeyId, titleId = titleId)
+
+ data class BlockAudioVideo(
+ override val prefKeyId: Int = R.string.pref_key_block_autoplay_audio_video,
+ override val titleId: Int = R.string.preference_block_autoplay_audio_video,
+ ) : SitePermissionOption(prefKeyId = prefKeyId, titleId = titleId)
+}
+
+sealed class SitePermissionOption(open val prefKeyId: Int, open val titleId: Int, open val summaryId: Int? = null) {
+
+ data class AskToAllow(
+ override val prefKeyId: Int = R.string.pref_key_ask_to_allow,
+ override val titleId: Int = R.string.preference_option_phone_feature_ask_to_allow,
+ override val summaryId: Int = R.string.preference_block_autoplay_audio_only_summary,
+ ) : SitePermissionOption(prefKeyId = prefKeyId, titleId = titleId)
+
+ data class Blocked(
+ override val prefKeyId: Int = R.string.pref_key_blocked,
+ override val titleId: Int = R.string.preference_option_phone_feature_blocked,
+ ) : SitePermissionOption(prefKeyId = prefKeyId, titleId = titleId)
+
+ data class Allowed(
+ override val prefKeyId: Int = R.string.pref_key_allowed,
+ override val titleId: Int = R.string.preference_option_phone_feature_allowed,
+ ) : SitePermissionOption(prefKeyId = prefKeyId, titleId = titleId)
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionsFragment.kt
new file mode 100644
index 0000000000..1815b2ea3c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/SitePermissionsFragment.kt
@@ -0,0 +1,63 @@
+/* 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.focus.settings.permissions
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.requirePreference
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsFragment
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsStorage
+import org.mozilla.focus.state.AppAction
+
+class SitePermissionsFragment : BaseSettingsFragment() {
+
+ @VisibleForTesting
+ internal lateinit var storage: SitePermissionOptionsStorage
+
+ @VisibleForTesting
+ internal fun getPreference(sitePermissionID: Int): Preference =
+ requirePreference(sitePermissionID)
+
+ override fun onStart() {
+ super.onStart()
+ showToolbar(getString(R.string.preference_site_permissions))
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.site_permissions, rootKey)
+ bindCategoryPhoneFeatures()
+ }
+
+ @VisibleForTesting
+ internal fun bindCategoryPhoneFeatures() {
+ SitePermission.values()
+ // Only AUTOPLAY should appear in the list AUTOPLAY_INAUDIBLE and AUTOPLAY_AUDIBLE
+ // shouldn't be bound
+ .filter { it != SitePermission.AUTOPLAY_INAUDIBLE && it != SitePermission.AUTOPLAY_AUDIBLE }
+ .forEach(::initPhoneFeature)
+ }
+
+ @VisibleForTesting
+ internal fun initPhoneFeature(sitePermission: SitePermission) {
+ storage = SitePermissionOptionsStorage(requireContext())
+ val preferencePhoneFeatures = getPreference(storage.getSitePermissionPreferenceId(sitePermission))
+
+ preferencePhoneFeatures.summary = storage.getSitePermissionOptionSelectedLabel(sitePermission)
+
+ preferencePhoneFeatures.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ navigateToSitePermissionOptionsScreen(sitePermission)
+ true
+ }
+ }
+
+ @VisibleForTesting
+ internal fun navigateToSitePermissionOptionsScreen(sitePermission: SitePermission) {
+ requireComponents.appStore.dispatch(AppAction.OpenSitePermissionOptionsScreen(sitePermission = sitePermission))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/DefaultSitePermissionOptionsScreenInteractor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/DefaultSitePermissionOptionsScreenInteractor.kt
new file mode 100644
index 0000000000..961a708c0f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/DefaultSitePermissionOptionsScreenInteractor.kt
@@ -0,0 +1,22 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+
+class DefaultSitePermissionOptionsScreenInteractor(
+ private val sitePermissionOptionsScreenStore: SitePermissionOptionsScreenStore,
+) {
+ fun handleSitePermissionOptionSelected(sitePermissionOption: SitePermissionOption) {
+ if (sitePermissionOptionsScreenStore.state.selectedSitePermissionOption == sitePermissionOption) {
+ return
+ }
+ sitePermissionOptionsScreenStore.dispatch(
+ SitePermissionOptionsScreenAction.Select(
+ selectedSitePermissionOption = sitePermissionOption,
+ ),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/HardwarePermissionCheckFeature.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/HardwarePermissionCheckFeature.kt
new file mode 100644
index 0000000000..a804491d35
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/HardwarePermissionCheckFeature.kt
@@ -0,0 +1,20 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+
+class HardwarePermissionCheckFeature(
+ val storage: SitePermissionOptionsStorage,
+ val store: SitePermissionOptionsScreenStore,
+ val sitePermission: SitePermission,
+) : DefaultLifecycleObserver {
+
+ override fun onStart(owner: LifecycleOwner) {
+ super.onStart(owner)
+ val isPermissionGranted = storage.isAndroidPermissionGranted(sitePermission)
+ store.dispatch(SitePermissionOptionsScreenAction.AndroidPermission(isPermissionGranted))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermission.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermission.kt
new file mode 100644
index 0000000000..f719be6e35
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermission.kt
@@ -0,0 +1,20 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import android.Manifest
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+enum class SitePermission(val androidPermissionsList: Array) : Parcelable {
+ CAMERA(arrayOf(Manifest.permission.CAMERA)),
+ LOCATION(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)),
+ MICROPHONE(arrayOf(Manifest.permission.RECORD_AUDIO)),
+ NOTIFICATION(emptyArray()),
+ AUTOPLAY(emptyArray()),
+ AUTOPLAY_AUDIBLE(emptyArray()),
+ AUTOPLAY_INAUDIBLE(emptyArray()),
+ MEDIA_KEY_SYSTEM_ACCESS(emptyArray()),
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionListItem.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionListItem.kt
new file mode 100644
index 0000000000..8528f167d2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionListItem.kt
@@ -0,0 +1,11 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+
+data class SitePermissionOptionListItem(
+ val sitePermissionOption: SitePermissionOption,
+ val onClick: (SitePermissionOption) -> Unit,
+)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragment.kt
new file mode 100644
index 0000000000..b8935f09cc
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragment.kt
@@ -0,0 +1,141 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import mozilla.components.lib.state.ext.observeAsComposableState
+import mozilla.components.support.utils.ext.getParcelableCompat
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.settings.BaseComposeFragment
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+
+class SitePermissionOptionsFragment : BaseComposeFragment() {
+
+ private lateinit var sitePermissionOptionsScreenStore: SitePermissionOptionsScreenStore
+ private lateinit var defaultSitePermissionOptionsScreenInteractor: DefaultSitePermissionOptionsScreenInteractor
+ private lateinit var hardwarePermissionCheckFeature: HardwarePermissionCheckFeature
+ private lateinit var sitePermissionOptionsStorage: SitePermissionOptionsStorage
+
+ private val sitePermission: SitePermission
+ get() = requireArguments().getParcelableCompat(SITE_PERMISSION, SitePermission::class.java)
+ ?: throw IllegalAccessError("Site permission is not set for fragment")
+
+ companion object {
+ const val FRAGMENT_TAG = "SitePermissionOptionsFragment"
+ private const val SITE_PERMISSION = "sitePermission"
+
+ fun addSitePermission(sitePermission: SitePermission): SitePermissionOptionsFragment {
+ val fragment = SitePermissionOptionsFragment()
+ fragment.arguments = Bundle().apply {
+ putParcelable(SITE_PERMISSION, sitePermission)
+ }
+ return fragment
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ sitePermissionOptionsStorage = SitePermissionOptionsStorage(context = requireContext())
+ sitePermissionOptionsScreenStore = SitePermissionOptionsScreenStore(
+ SitePermissionOptionsScreenState(),
+ listOf(
+ SitePermissionOptionsStorageMiddleware(
+ sitePermission = sitePermission,
+ storage = sitePermissionOptionsStorage,
+ ),
+ ),
+ )
+ defaultSitePermissionOptionsScreenInteractor = DefaultSitePermissionOptionsScreenInteractor(
+ sitePermissionOptionsScreenStore = sitePermissionOptionsScreenStore,
+ )
+ hardwarePermissionCheckFeature = HardwarePermissionCheckFeature(
+ storage = sitePermissionOptionsStorage,
+ store = sitePermissionOptionsScreenStore,
+ sitePermission = sitePermission,
+ )
+ lifecycle.addObserver(hardwarePermissionCheckFeature)
+ }
+
+ override val titleText: String
+ get() = sitePermissionOptionsScreenStore.state.sitePermissionLabel
+
+ override fun onNavigateUp(): () -> Unit {
+ return {
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(Screen.Settings.Page.SitePermissions),
+ )
+ }
+ }
+
+ @Composable
+ override fun Content() {
+ val sitePermissionOptionsList = sitePermissionOptionsScreenStore.observeAsComposableState { state ->
+ state.sitePermissionOptionList
+ }.value
+ val sitePermissionOptionSelected = sitePermissionOptionsScreenStore.observeAsComposableState { state ->
+ state.selectedSitePermissionOption
+ }.value
+ val isAndroidPermissionGranted = sitePermissionOptionsScreenStore.observeAsComposableState { state ->
+ state.isAndroidPermissionGranted
+ }.value
+ if (
+ sitePermissionOptionSelected != null &&
+ sitePermissionOptionsList != null &&
+ isAndroidPermissionGranted != null
+ ) {
+ CreateOptionsPermissionList(
+ sitePermissionOptionSelected,
+ sitePermissionOptionsList,
+ isAndroidPermissionGranted,
+ )
+ }
+ }
+
+ @Composable
+ private fun CreateOptionsPermissionList(
+ sitePermissionOptionSelected: SitePermissionOption,
+ sitePermissionOptionsList: List,
+ isAndroidPermissionGranted: Boolean,
+ ) {
+ val state = remember {
+ mutableStateOf(sitePermissionOptionSelected.prefKeyId)
+ }
+ val optionsListItems = ArrayList()
+ sitePermissionOptionsList.forEach { sitePermissionOption ->
+ val sitePermissionOptionListItem = SitePermissionOptionListItem(
+ sitePermissionOption = sitePermissionOption,
+ onClick = {
+ state.value = sitePermissionOption.prefKeyId
+ defaultSitePermissionOptionsScreenInteractor.handleSitePermissionOptionSelected(
+ sitePermissionOption,
+ )
+ requireComponents.appStore.dispatch(AppAction.SitePermissionOptionChange(true))
+ },
+ )
+ optionsListItems.add(sitePermissionOptionListItem)
+ }
+ OptionsPermissionList(
+ optionsListItems = optionsListItems,
+ state = state,
+ goToPhoneSettings = { openSettings() },
+ permissionLabel = sitePermissionOptionsScreenStore.state.sitePermissionLabel,
+ componentPermissionBlockedByAndroidVisibility = !isAndroidPermissionGranted,
+ )
+ }
+
+ private fun openSettings() {
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ val uri = Uri.fromParts("package", requireContext().packageName, null)
+ intent.data = uri
+ startActivity(intent)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt
new file mode 100644
index 0000000000..0bfae428f2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt
@@ -0,0 +1,280 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.RadioButton
+import androidx.compose.material.RadioButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.focus.R
+import org.mozilla.focus.settings.permissions.AutoplayOption
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+
+private fun getPermissionOptionsList(): List {
+ return mutableListOf().apply {
+ add(SitePermissionOptionListItem(AutoplayOption.AllowAudioVideo(), onClick = {}))
+ add(SitePermissionOptionListItem(AutoplayOption.BlockAudioOnly(), onClick = {}))
+ add(SitePermissionOptionListItem(AutoplayOption.BlockAudioVideo(), onClick = {}))
+ }
+}
+
+@Composable
+@Preview
+private fun PermissionOptionsListComposablePreview() {
+ FocusTheme {
+ val state = remember {
+ mutableStateOf(AutoplayOption.BlockAudioOnly().prefKeyId)
+ }
+ OptionsPermissionList(
+ optionsListItems = getPermissionOptionsList(),
+ state = state,
+ permissionLabel = "Camera",
+ goToPhoneSettings = {},
+ componentPermissionBlockedByAndroidVisibility = true,
+ )
+ }
+}
+
+/**
+ * Displays a list of Site Permission Options
+ *
+ * @param optionsListItems The list of Site Permission Options items to be displayed.
+ * @param state the current Option
+ */
+@Composable
+fun OptionsPermissionList(
+ optionsListItems: List,
+ state: MutableState,
+ permissionLabel: String?,
+ goToPhoneSettings: () -> Unit,
+ componentPermissionBlockedByAndroidVisibility: Boolean,
+) {
+ FocusTheme {
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .background(
+ colorResource(R.color.settings_background),
+ shape = RectangleShape,
+ ),
+ ) {
+ LazyColumn(
+ contentPadding = PaddingValues(horizontal = 12.dp),
+ ) {
+ items(optionsListItems) { item ->
+ OptionPermission(
+ sitePermissionOption = item.sitePermissionOption,
+ isSelected = state.value == item.sitePermissionOption.prefKeyId,
+ onClick = item.onClick,
+ )
+ }
+ item {
+ if (componentPermissionBlockedByAndroidVisibility) {
+ ComponentPermissionBlockedByAndroid(goToPhoneSettings, permissionLabel)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun OptionPermission(
+ sitePermissionOption: SitePermissionOption,
+ isSelected: Boolean,
+ onClick: (SitePermissionOption) -> Unit,
+) {
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .selectable(
+ selected = isSelected,
+ onClick = { onClick(sitePermissionOption) },
+ role = Role.RadioButton,
+ ),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ // When we use the onClick parameter in the selectable modifier we can use the onClick = null
+ // in the RadioButton component.
+ RadioButton(
+ selected = isSelected,
+ colors = RadioButtonDefaults.colors(selectedColor = focusColors.radioButtonSelected),
+ onClick = null,
+ )
+ OptionPermissionDisplayName(
+ sitePermissionOption = sitePermissionOption,
+ )
+ }
+}
+
+@Composable
+private fun OptionPermissionDisplayName(sitePermissionOption: SitePermissionOption) {
+ Column(modifier = Modifier.padding(10.dp)) {
+ Text(
+ textAlign = TextAlign.Start,
+ color = focusColors.settingsTextColor,
+ text = AnnotatedString(LocalContext.current.resources.getString(sitePermissionOption.titleId)),
+ style = TextStyle(
+ fontSize = 16.sp,
+ ),
+ modifier = Modifier
+ .padding(start = 8.dp, end = 8.dp),
+ )
+ sitePermissionOption.summaryId?.let {
+ Text(
+ textAlign = TextAlign.Start,
+ text = AnnotatedString(LocalContext.current.resources.getString(it)),
+ color = focusColors.settingsTextSummaryColor,
+ style = TextStyle(
+ fontSize = 14.sp,
+ ),
+ modifier = Modifier
+ .padding(start = 8.dp, end = 8.dp),
+ )
+ }
+ }
+}
+
+/**
+ * Displays a component if the Site Permission needs user approval from Phone Settings
+ * This is needed for Permissions like Camera ,Location, Microphone
+ *
+ * @param goToPhoneSettings callback when the user press Go to Settings button
+ * @param permissionLabel label for the Site Permission
+ */
+
+@Composable
+private fun ComponentPermissionBlockedByAndroid(goToPhoneSettings: () -> Unit, permissionLabel: String?) {
+ Column(
+ modifier = Modifier
+ .background(colorResource(R.color.settings_background), shape = RectangleShape)
+ .fillMaxWidth()
+ .padding(top = 16.dp)
+ .wrapContentHeight(),
+ ) {
+ ComponentPermissionBlockedByAndroidText(
+ stringRes = R.string.phone_feature_blocked_by_android,
+ permissionLabel,
+ 16.dp,
+ )
+ ComponentPermissionBlockedByAndroidText(
+ stringRes = R.string.phone_feature_blocked_intro,
+ permissionLabel,
+ 16.dp,
+ )
+ ComponentPermissionBlockedByAndroidText(
+ stringRes = R.string.phone_feature_blocked_step_settings,
+ permissionLabel,
+ 8.dp,
+ )
+ ComponentPermissionBlockedByAndroidText(
+ stringRes = R.string.phone_feature_blocked_step_permissions,
+ permissionLabel,
+ 8.dp,
+ )
+ ComponentPermissionBlockedByAndroidText(
+ stringRes = R.string.phone_feature_blocked_step_feature,
+ permissionLabel,
+ )
+ ComponentPermissionBlockedByAndroidButton(
+ goToPhoneSettings = goToPhoneSettings,
+ )
+ }
+}
+
+@Composable
+private fun ComponentPermissionBlockedByAndroidButton(goToPhoneSettings: () -> Unit) {
+ Button(
+ onClick = goToPhoneSettings,
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = PhotonColors.LightGrey50,
+ ),
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth(),
+ ) {
+ Text(
+ color = PhotonColors.Ink20,
+ text = AnnotatedString(
+ LocalContext.current.resources.getString(
+ R.string.phone_feature_go_to_settings,
+ ),
+ ),
+ )
+ }
+}
+
+@Composable
+private fun ComponentPermissionBlockedByAndroidText(
+ stringRes: Int,
+ permissionLabel: String?,
+ bottomPadding: Dp = 0.dp,
+) {
+ Text(
+ textAlign = TextAlign.Start,
+ color = focusColors.settingsTextColor,
+ text = LocalContext.current.getString(stringRes, permissionLabel).parseBold(),
+ style = TextStyle(
+ fontSize = 16.sp,
+ ),
+ modifier = Modifier.padding(start = 55.dp, end = 16.dp, bottom = bottomPadding),
+ )
+}
+
+private fun String.parseBold(): AnnotatedString {
+ val parts = this.split("", " ")
+ return buildAnnotatedString {
+ var bold = false
+ for (part in parts) {
+ if (bold) {
+ withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
+ append(part)
+ }
+ } else {
+ append(part)
+ }
+ bold = !bold
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsScreenStore.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsScreenStore.kt
new file mode 100644
index 0000000000..e69e02f9d0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsScreenStore.kt
@@ -0,0 +1,70 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import mozilla.components.lib.state.Action
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.State
+import mozilla.components.lib.state.Store
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+
+class SitePermissionOptionsScreenStore(
+ initialState: SitePermissionOptionsScreenState,
+ middlewares: List> = emptyList(),
+) : Store(
+ initialState,
+ ::sitePermissionOptionsScreenReducer,
+ middlewares,
+) {
+ init {
+ dispatch(SitePermissionOptionsScreenAction.InitSitePermissionOptions)
+ }
+}
+
+data class SitePermissionOptionsScreenState(
+ val sitePermissionOptionList: List = emptyList(),
+ val selectedSitePermissionOption: SitePermissionOption? = null,
+ val sitePermissionLabel: String = "",
+ val isAndroidPermissionGranted: Boolean = false,
+) : State
+
+sealed class SitePermissionOptionsScreenAction : Action {
+ object InitSitePermissionOptions : SitePermissionOptionsScreenAction()
+ data class Select(val selectedSitePermissionOption: SitePermissionOption) : SitePermissionOptionsScreenAction()
+ data class AndroidPermission(val isAndroidPermissionGranted: Boolean) : SitePermissionOptionsScreenAction()
+ data class UpdateSitePermissionOptions(
+ val sitePermissionOptionsList: List,
+ val selectedSitePermissionOption: SitePermissionOption,
+ val sitePermissionLabel: String,
+ val isAndroidPermissionGranted: Boolean,
+ ) : SitePermissionOptionsScreenAction()
+}
+
+private fun sitePermissionOptionsScreenReducer(
+ state: SitePermissionOptionsScreenState,
+ action: SitePermissionOptionsScreenAction,
+): SitePermissionOptionsScreenState {
+ return when (action) {
+ is SitePermissionOptionsScreenAction.Select -> {
+ state.copy(selectedSitePermissionOption = action.selectedSitePermissionOption)
+ }
+ is SitePermissionOptionsScreenAction.UpdateSitePermissionOptions -> {
+ state.copy(
+ sitePermissionOptionList = action.sitePermissionOptionsList,
+ selectedSitePermissionOption = action.selectedSitePermissionOption,
+ sitePermissionLabel = action.sitePermissionLabel,
+ isAndroidPermissionGranted = action.isAndroidPermissionGranted,
+ )
+ }
+
+ SitePermissionOptionsScreenAction.InitSitePermissionOptions -> {
+ throw IllegalStateException(
+ "You need to add SitePermissionsOptionsMiddleware to your SitePermissionsOptionsScreenStore. ($action)",
+ )
+ }
+ is SitePermissionOptionsScreenAction.AndroidPermission -> {
+ state.copy(isAndroidPermissionGranted = action.isAndroidPermissionGranted)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorage.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorage.kt
new file mode 100644
index 0000000000..68b8d5da7d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorage.kt
@@ -0,0 +1,215 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import android.content.Context
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import androidx.preference.PreferenceManager
+import mozilla.components.feature.sitepermissions.SitePermissionsRules
+import mozilla.components.support.ktx.android.content.isPermissionGranted
+import org.mozilla.focus.R
+import org.mozilla.focus.settings.permissions.AutoplayOption
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+
+@Suppress("TooManyFunctions")
+class SitePermissionOptionsStorage(private val context: Context) {
+
+ /**
+ * Return the label for Site Permission Option selected that will
+ * appear in Site Permissions Screen
+ */
+ fun getSitePermissionOptionSelectedLabel(sitePermission: SitePermission): String {
+ return if (isAndroidPermissionGranted(sitePermission)) {
+ context.getString(permissionSelectedOption(sitePermission).titleId)
+ } else {
+ context.getString(R.string.phone_feature_blocked_by_android)
+ }
+ }
+
+ fun isAndroidPermissionGranted(sitePermission: SitePermission): Boolean {
+ return context.isPermissionGranted(sitePermission.androidPermissionsList.asIterable())
+ }
+
+ fun getSitePermissionLabel(sitePermission: SitePermission): String {
+ return when (sitePermission) {
+ SitePermission.CAMERA -> context.getString(R.string.preference_phone_feature_camera)
+ SitePermission.LOCATION -> context.getString(R.string.preference_phone_feature_location)
+ SitePermission.MICROPHONE -> context.getString(R.string.preference_phone_feature_microphone)
+ SitePermission.NOTIFICATION -> context.getString(R.string.preference_phone_feature_notification)
+ SitePermission.MEDIA_KEY_SYSTEM_ACCESS -> context.getString(
+ R.string.preference_phone_feature_media_key_system_access,
+ )
+ SitePermission.AUTOPLAY, SitePermission.AUTOPLAY_AUDIBLE, SitePermission.AUTOPLAY_INAUDIBLE ->
+ context.getString(R.string.preference_autoplay)
+ }
+ }
+
+ /**
+ * Return the available Options for a Site Permission
+ */
+ fun getSitePermissionOptions(sitePermission: SitePermission): List {
+ return when (sitePermission) {
+ SitePermission.CAMERA -> listOf(
+ SitePermissionOption.AskToAllow(),
+ SitePermissionOption.Blocked(),
+ )
+ SitePermission.LOCATION -> listOf(
+ SitePermissionOption.AskToAllow(),
+ SitePermissionOption.Blocked(),
+ )
+ SitePermission.MICROPHONE -> listOf(
+ SitePermissionOption.AskToAllow(),
+ SitePermissionOption.Blocked(),
+ )
+ SitePermission.NOTIFICATION -> listOf(
+ SitePermissionOption.AskToAllow(),
+ SitePermissionOption.Blocked(),
+ )
+ SitePermission.MEDIA_KEY_SYSTEM_ACCESS -> listOf(
+ SitePermissionOption.AskToAllow(),
+ SitePermissionOption.Blocked(),
+ SitePermissionOption.Allowed(),
+ )
+ SitePermission.AUTOPLAY, SitePermission.AUTOPLAY_AUDIBLE, SitePermission.AUTOPLAY_INAUDIBLE ->
+ listOf(
+ AutoplayOption.AllowAudioVideo(),
+ AutoplayOption.BlockAudioOnly(),
+ AutoplayOption.BlockAudioVideo(),
+ )
+ }
+ }
+
+ /**
+ * Return the default Option for a Site Permission if the user doesn't select nothing
+ */
+ @VisibleForTesting
+ internal fun getSitePermissionDefaultOption(sitePermission: SitePermission): SitePermissionOption {
+ return when (sitePermission) {
+ SitePermission.CAMERA -> SitePermissionOption.AskToAllow()
+ SitePermission.LOCATION -> SitePermissionOption.AskToAllow()
+ SitePermission.MICROPHONE -> SitePermissionOption.AskToAllow()
+ SitePermission.NOTIFICATION -> SitePermissionOption.AskToAllow()
+ SitePermission.MEDIA_KEY_SYSTEM_ACCESS -> SitePermissionOption.AskToAllow()
+ SitePermission.AUTOPLAY, SitePermission.AUTOPLAY_AUDIBLE, SitePermission.AUTOPLAY_INAUDIBLE ->
+ AutoplayOption.BlockAudioOnly()
+ }
+ }
+
+ /**
+ * Return the user selected Option for a Site Permission or the default one if the user doesn't
+ * select one
+ */
+ internal fun permissionSelectedOption(sitePermission: SitePermission) =
+ when (permissionSelectedOptionByKey(getSitePermissionPreferenceId(sitePermission))) {
+ context.getString(R.string.pref_key_allow_autoplay_audio_video) -> AutoplayOption.AllowAudioVideo()
+ context.getString(R.string.pref_key_block_autoplay_audio_video) -> AutoplayOption.BlockAudioVideo()
+ context.getString(R.string.pref_key_allowed) -> SitePermissionOption.Allowed()
+ context.getString(R.string.pref_key_blocked) -> SitePermissionOption.Blocked()
+ context.getString(R.string.pref_key_ask_to_allow) -> SitePermissionOption.AskToAllow()
+ context.getString(R.string.pref_key_block_autoplay_audio_only) -> AutoplayOption.BlockAudioOnly()
+ else -> {
+ getSitePermissionDefaultOption(sitePermission)
+ }
+ }
+
+ /**
+ * Returns Site Permission corresponding resource ID from preference_keys
+ */
+ @StringRes
+ fun getSitePermissionPreferenceId(sitePermission: SitePermission): Int {
+ return when (sitePermission) {
+ SitePermission.CAMERA -> R.string.pref_key_phone_feature_camera
+ SitePermission.LOCATION -> R.string.pref_key_phone_feature_location
+ SitePermission.MICROPHONE -> R.string.pref_key_phone_feature_microphone
+ SitePermission.NOTIFICATION -> R.string.pref_key_phone_feature_notification
+ SitePermission.AUTOPLAY -> R.string.pref_key_autoplay
+ SitePermission.AUTOPLAY_AUDIBLE -> R.string.pref_key_allow_autoplay_audio_video
+ SitePermission.AUTOPLAY_INAUDIBLE -> R.string.pref_key_block_autoplay_audio_video
+ SitePermission.MEDIA_KEY_SYSTEM_ACCESS -> R.string.pref_key_browser_feature_media_key_system_access
+ }
+ }
+
+ /**
+ * Saves the current Site Permission Option
+ *
+ * @param sitePermissionOption to be Saved
+ * @param sitePermission the corresponding Site Permission
+ */
+ fun saveCurrentSitePermissionOptionInSharePref(
+ sitePermissionOption: SitePermissionOption,
+ sitePermission: SitePermission,
+ ) {
+ val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
+ with(sharedPref.edit()) {
+ putString(
+ context.getString(getSitePermissionPreferenceId(sitePermission)),
+ context.getString(sitePermissionOption.prefKeyId),
+ )
+ apply()
+ }
+ }
+
+ @VisibleForTesting
+ internal fun permissionSelectedOptionByKey(
+ sitePermissionKey: Int,
+ ): String {
+ val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
+ return sharedPref.getString(context.getString(sitePermissionKey), "") ?: ""
+ }
+
+ private fun getAutoplayRules(): Pair {
+ return when (permissionSelectedOption(SitePermission.AUTOPLAY)) {
+ is AutoplayOption.AllowAudioVideo -> Pair(
+ SitePermissionsRules.AutoplayAction.ALLOWED,
+ SitePermissionsRules.AutoplayAction.ALLOWED,
+ )
+
+ is AutoplayOption.BlockAudioVideo -> Pair(
+ SitePermissionsRules.AutoplayAction.BLOCKED,
+ SitePermissionsRules.AutoplayAction.BLOCKED,
+ )
+
+ else -> Pair(
+ SitePermissionsRules.AutoplayAction.BLOCKED,
+ SitePermissionsRules.AutoplayAction.ALLOWED,
+ )
+ }
+ }
+
+ fun getSitePermissionsSettingsRules() = SitePermissionsRules(
+ notification = getSitePermissionRules(SitePermission.NOTIFICATION),
+ microphone = getSitePermissionRules(SitePermission.MICROPHONE),
+ location = getSitePermissionRules(SitePermission.LOCATION),
+ camera = getSitePermissionRules(SitePermission.CAMERA),
+ autoplayAudible = getAutoplayRules().first,
+ autoplayInaudible = getAutoplayRules().second,
+ persistentStorage = SitePermissionsRules.Action.BLOCKED,
+ mediaKeySystemAccess = getSitePermissionRules(SitePermission.MEDIA_KEY_SYSTEM_ACCESS),
+ crossOriginStorageAccess = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ )
+
+ private fun getSitePermissionRules(sitePermission: SitePermission): SitePermissionsRules.Action {
+ return when (permissionSelectedOption(sitePermission)) {
+ is SitePermissionOption.Allowed -> SitePermissionsRules.Action.ALLOWED
+ is SitePermissionOption.AskToAllow -> SitePermissionsRules.Action.ASK_TO_ALLOW
+ is SitePermissionOption.Blocked -> SitePermissionsRules.Action.BLOCKED
+ else -> {
+ SitePermissionsRules.Action.BLOCKED
+ }
+ }
+ }
+
+ fun isSitePermissionNotBlocked(permissionsList: Array): Boolean {
+ SitePermission.values().forEach { sitePermission ->
+ if (
+ sitePermission.androidPermissionsList.intersect(permissionsList.toSet()).isNotEmpty() &&
+ getSitePermissionRules(sitePermission) != SitePermissionsRules.Action.BLOCKED
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorageMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorageMiddleware.kt
new file mode 100644
index 0000000000..98ec7c7dbd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsStorageMiddleware.kt
@@ -0,0 +1,43 @@
+/* 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.focus.settings.permissions.permissionoptions
+
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+
+class SitePermissionOptionsStorageMiddleware(
+ val sitePermission: SitePermission,
+ val storage: SitePermissionOptionsStorage,
+) :
+ Middleware {
+
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (SitePermissionOptionsScreenAction) -> Unit,
+ action: SitePermissionOptionsScreenAction,
+ ) {
+ when (action) {
+ is SitePermissionOptionsScreenAction.Select -> {
+ storage.saveCurrentSitePermissionOptionInSharePref(
+ action.selectedSitePermissionOption,
+ sitePermission = sitePermission,
+ )
+ next(action)
+ }
+ is SitePermissionOptionsScreenAction.InitSitePermissionOptions -> {
+ context.dispatch(
+ SitePermissionOptionsScreenAction.UpdateSitePermissionOptions(
+ storage.getSitePermissionOptions(sitePermission),
+ storage.permissionSelectedOption(sitePermission),
+ storage.getSitePermissionLabel(sitePermission),
+ storage.isAndroidPermissionGranted(sitePermission),
+ ),
+ )
+ }
+ else -> {
+ next(action)
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/ConnectionDetailsPanel.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/ConnectionDetailsPanel.kt
new file mode 100644
index 0000000000..07ca9aa9eb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/ConnectionDetailsPanel.kt
@@ -0,0 +1,82 @@
+/* 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.focus.settings.privacy
+
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+import androidx.appcompat.content.res.AppCompatResources
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import mozilla.components.browser.icons.IconRequest
+import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.ConnectionDetailsBinding
+import org.mozilla.focus.ext.components
+
+@SuppressWarnings("LongParameterList")
+class ConnectionDetailsPanel(
+ context: Context,
+ private val tabTitle: String,
+ private val tabUrl: String,
+ private val isConnectionSecure: Boolean,
+ private val goBack: () -> Unit,
+) : BottomSheetDialog(context) {
+
+ private var binding: ConnectionDetailsBinding =
+ ConnectionDetailsBinding.inflate(layoutInflater, null, false)
+
+ init {
+ setContentView(binding.root)
+ expandBottomSheet()
+
+ updateSiteInfo()
+ updateConnectionState()
+ setListeners()
+ }
+
+ private fun expandBottomSheet() {
+ val bottomSheet =
+ findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout
+ BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
+ }
+
+ private fun updateSiteInfo() {
+ binding.siteTitle.text = tabTitle
+ binding.siteFullUrl.text = tabUrl
+
+ context.components.icons.loadIntoView(
+ binding.siteFavicon,
+ IconRequest(tabUrl, isPrivate = true),
+ )
+ }
+
+ private fun updateConnectionState() {
+ binding.securityInfo.text = if (isConnectionSecure) {
+ context.getString(R.string.secure_connection)
+ } else {
+ context.getString(R.string.insecure_connection)
+ }
+
+ val securityIcon = if (isConnectionSecure) {
+ AppCompatResources.getDrawable(context, R.drawable.mozac_ic_lock_24)
+ } else {
+ AppCompatResources.getDrawable(context, R.drawable.mozac_ic_warning_fill_24)
+ }
+
+ binding.securityInfo.putCompoundDrawablesRelativeWithIntrinsicBounds(
+ start = securityIcon,
+ end = null,
+ top = null,
+ bottom = null,
+ )
+ }
+
+ private fun setListeners() {
+ binding.detailsBack.setOnClickListener {
+ goBack.invoke()
+ dismiss()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceSwitch.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceSwitch.kt
new file mode 100644
index 0000000000..ca3cf49f10
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceSwitch.kt
@@ -0,0 +1,68 @@
+/* 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.focus.settings.privacy
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.SwitchCompat
+import androidx.core.content.edit
+import androidx.core.content.withStyledAttributes
+import androidx.preference.PreferenceManager
+import org.mozilla.focus.R
+
+class PreferenceSwitch(
+ context: Context,
+ attrs: AttributeSet,
+) : SwitchCompat(context, attrs) {
+
+ private var clickListener: (() -> Unit)? = null
+ var key: Int = 0
+ var title: Int = 0
+ var description: Int = 0
+
+ init {
+ context.withStyledAttributes(
+ attrs,
+ R.styleable.PreferenceSwitch,
+ 0,
+ 0,
+ ) {
+ key = getResourceId(R.styleable.PreferenceSwitch_preferenceKey, 0)
+ title = getResourceId(R.styleable.PreferenceSwitch_preferenceKeyTitle, 0)
+ description =
+ getResourceId(R.styleable.PreferenceSwitch_preferenceKeyDescription, 0)
+ }
+ }
+
+ init {
+ setInitialValue()
+ setOnCheckedChangeListener(null)
+ setOnClickListener {
+ togglePreferenceValue(this.isChecked)
+ clickListener?.invoke()
+ }
+
+ if (title != 0) {
+ this.text = context.getString(title)
+ }
+ }
+
+ private fun setInitialValue() {
+ this.isChecked = PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(key), true)
+ }
+
+ fun onClickListener(listener: () -> Unit) {
+ clickListener = listener
+ }
+
+ private fun togglePreferenceValue(isChecked: Boolean) {
+ this.isChecked = isChecked
+
+ PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
+ putBoolean(context.getString(key), isChecked)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceToolTipCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceToolTipCompose.kt
new file mode 100644
index 0000000000..010d04446a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PreferenceToolTipCompose.kt
@@ -0,0 +1,137 @@
+/* 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.focus.settings.privacy
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FocusPreferenceComposeLayoutBinding
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+
+class PreferenceToolTipCompose(context: Context, attrs: AttributeSet?) :
+ Preference(context, attrs) {
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ val binding = FocusPreferenceComposeLayoutBinding.bind(holder.itemView)
+ binding.toolTipContent.setContent {
+ ToolTipContent(
+ onDismissButton = {
+ context.settings.shouldShowPrivacySecuritySettingsToolTip = false
+ preferenceManager.preferenceScreen.removePreference(this)
+ },
+ )
+ }
+ }
+}
+
+@Composable
+@Preview
+private fun ToolTipContentPreview() {
+ ToolTipContent {
+ }
+}
+
+/**
+ * Displays a tool tip for Privacy Security Settings screen.
+ *
+ * @param onDismissButton Callback when the "x" Button from tool tip is pressed.
+ */
+@Composable
+fun ToolTipContent(onDismissButton: () -> Unit) {
+ FocusTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentSize(Alignment.Center),
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(16.dp)
+ .clip(RoundedCornerShape(10.dp))
+ .background(
+ shape = RoundedCornerShape(10.dp),
+ brush = Brush.linearGradient(
+ colors = listOf(
+ colorResource(R.color.cfr_pop_up_shape_end_color),
+ colorResource(R.color.cfr_pop_up_shape_start_color),
+ ),
+ end = Offset(0f, Float.POSITIVE_INFINITY),
+ start = Offset(Float.POSITIVE_INFINITY, 0f),
+ ),
+ ),
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ Column(
+ modifier = Modifier
+ .wrapContentHeight()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(
+ 10.dp,
+ ),
+ ) {
+ Text(
+ text = stringResource(R.string.tool_tip_title),
+ fontSize = 16.sp,
+ letterSpacing = 0.5.sp,
+ color = focusColors.privacySecuritySettingsToolTip,
+ fontWeight = FontWeight.Bold,
+ lineHeight = 20.sp,
+ )
+ Text(
+ text = stringResource(R.string.tool_tip_message),
+ fontSize = 14.sp,
+ color = focusColors.privacySecuritySettingsToolTip,
+ lineHeight = 20.sp,
+ )
+ }
+ IconButton(
+ onClick = onDismissButton,
+ modifier = Modifier.align(
+ Alignment.TopEnd,
+ ),
+ ) {
+ Icon(
+ Icons.Filled.Close,
+ contentDescription = stringResource(R.string.tool_tip_dismiss_button_content_description),
+ tint = focusColors.privacySecuritySettingsToolTip,
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PrivacySecuritySettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PrivacySecuritySettingsFragment.kt
new file mode 100644
index 0000000000..6d3e7ba300
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/PrivacySecuritySettingsFragment.kt
@@ -0,0 +1,254 @@
+/* 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.focus.settings.privacy
+
+import android.content.SharedPreferences
+import android.os.Build
+import android.os.Bundle
+import androidx.preference.Preference
+import androidx.preference.SwitchPreferenceCompat
+import mozilla.components.lib.auth.canUseBiometricFeature
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.CookieBanner
+import org.mozilla.focus.GleanMetrics.PrivacySettings
+import org.mozilla.focus.GleanMetrics.TrackingProtectionExceptions
+import org.mozilla.focus.R
+import org.mozilla.focus.cookiebanner.CookieBannerOption
+import org.mozilla.focus.engine.EngineSharedPreferencesListener
+import org.mozilla.focus.ext.requireComponents
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.settings.BaseSettingsFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.widget.CookiesPreference
+
+class PrivacySecuritySettingsFragment :
+ BaseSettingsFragment(),
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ override fun onCreatePreferences(p0: Bundle?, p1: String?) {
+ addPreferencesFromResource(R.xml.privacy_security_settings)
+
+ val biometricPreference: SwitchPreferenceCompat? =
+ findPreference(getString(R.string.pref_key_biometric))
+ val appName = getString(R.string.app_name)
+ biometricPreference?.summary =
+ getString(R.string.preference_security_biometric_summary2, appName)
+
+ // Remove the biometric toggle if the software or hardware do not support it
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !requireContext().canUseBiometricFeature()
+ ) {
+ biometricPreference?.let { preferenceScreen.removePreference(it) }
+ }
+ if (!FocusNimbus.features.onboarding.value().isCfrEnabled ||
+ !requireContext().settings.shouldShowPrivacySecuritySettingsToolTip
+ ) {
+ val privacySecuritySettingsToolTip: PreferenceToolTipCompose? =
+ findPreference(getString(R.string.pref_key_tool_tip))
+ privacySecuritySettingsToolTip?.let { preferenceScreen.removePreference(it) }
+ }
+
+ val preferencesListener = EngineSharedPreferencesListener(requireContext())
+
+ val cookiesPreference =
+ findPreference(getString(R.string.pref_key_performance_enable_cookies)) as? CookiesPreference
+ cookiesPreference?.updateSummary()
+
+ val safeBrowsingSwitchPreference =
+ findPreference(getString(R.string.pref_key_safe_browsing)) as? SwitchPreferenceCompat
+ val javaScriptPreference =
+ findPreference(getString(R.string.pref_key_performance_block_javascript)) as? SwitchPreferenceCompat
+ val webFontsPreference =
+ findPreference(getString(R.string.pref_key_performance_block_webfonts)) as? SwitchPreferenceCompat
+ val cookieBannerPreference = findPreference(getString(R.string.pref_key_cookie_banner_settings))
+
+ cookiesPreference?.onPreferenceChangeListener = preferencesListener
+ safeBrowsingSwitchPreference?.onPreferenceChangeListener = preferencesListener
+ javaScriptPreference?.onPreferenceChangeListener = preferencesListener
+ webFontsPreference?.onPreferenceChangeListener = preferencesListener
+
+ cookieBannerPreference?.isVisible = requireContext().settings.isCookieBannerEnable
+ if (requireContext().settings.getCurrentCookieBannerOptionFromSharePref() ==
+ CookieBannerOption.CookieBannerDisabled()
+ ) {
+ cookieBannerPreference?.summary = getString(R.string.preferences_cookie_banner_summary_off)
+ } else {
+ cookieBannerPreference?.summary = getString(R.string.preferences_cookie_banner_summary_on)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateBiometricsToggleAvailability()
+ updateStealthToggleAvailability()
+ updateExceptionSettingAvailability()
+
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+
+ // Update title and icons when returning to fragments.
+ showToolbar(getString(R.string.preference_privacy_and_security_header))
+ }
+
+ override fun onPause() {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPause()
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ key?.let {
+ recordTelemetry(it, sharedPreferences.all[key])
+ }
+ updateStealthToggleAvailability()
+ }
+
+ private fun recordTelemetry(key: String, newValue: Any?) {
+ when (key) {
+ getString(R.string.pref_key_telemetry) -> PrivacySettings.telemetrySettingChanged.record(
+ PrivacySettings.TelemetrySettingChangedExtra(newValue as? Boolean),
+ )
+ getString(R.string.pref_key_safe_browsing) -> PrivacySettings.safeBrowsingSettingChanged.record(
+ PrivacySettings.SafeBrowsingSettingChangedExtra(newValue as? Boolean),
+ )
+ getString(R.string.pref_key_biometric) -> PrivacySettings.unlockSettingChanged.record(
+ PrivacySettings.UnlockSettingChangedExtra(newValue as? Boolean),
+ )
+ getString(R.string.pref_key_secure) -> PrivacySettings.stealthSettingChanged.record(
+ PrivacySettings.StealthSettingChangedExtra(newValue as? Boolean),
+ )
+ getString(R.string.pref_key_performance_enable_cookies) -> PrivacySettings.blockCookiesChanged.record(
+ PrivacySettings.BlockCookiesChangedExtra(newValue as? String),
+ )
+ else -> {
+ // Telemetry for the change is recorded elsewhere.
+ }
+ }
+ }
+
+ private fun updateBiometricsToggleAvailability() {
+ val switch =
+ preferenceScreen.findPreference(resources.getString(R.string.pref_key_biometric))
+ as? SwitchPreferenceCompat
+
+ if (!requireContext().canUseBiometricFeature()) {
+ switch?.isChecked = false
+ switch?.isEnabled = false
+ preferenceManager.sharedPreferences
+ ?.edit()
+ ?.putBoolean(resources.getString(R.string.pref_key_biometric), false)
+ ?.apply()
+ } else {
+ switch?.isEnabled = true
+ }
+ }
+
+ private fun updateExceptionSettingAvailability() {
+ val exceptionsPreference: Preference? =
+ findPreference(getString(R.string.pref_key_screen_exceptions))
+ exceptionsPreference?.isEnabled = false
+
+ requireComponents.trackingProtectionUseCases.fetchExceptions.invoke { exceptions ->
+ exceptionsPreference?.isEnabled = exceptions.isNotEmpty()
+ }
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ val settings = requireContext().settings
+ val engineSharedPreferencesListener = EngineSharedPreferencesListener(requireContext())
+ when (preference.key) {
+ resources.getString(R.string.pref_key_screen_exceptions) -> {
+ TrackingProtectionExceptions.allowListOpened.record(NoExtras())
+
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.PrivacyExceptions),
+ )
+ }
+ resources.getString(R.string.pref_key_secure),
+ resources.getString(R.string.pref_key_biometric),
+ -> {
+ // We need to recreate the activity to apply the SECURE flags.
+ requireActivity().recreate()
+ }
+
+ resources.getString(R.string.pref_key_privacy_block_social) ->
+ engineSharedPreferencesListener.updateTrackingProtectionPolicy(
+ EngineSharedPreferencesListener.ChangeSource.SETTINGS.source,
+ EngineSharedPreferencesListener.TrackerChanged.SOCIAL.tracker,
+ settings.shouldBlockSocialTrackers(),
+ )
+
+ resources.getString(R.string.pref_key_privacy_block_ads) ->
+ engineSharedPreferencesListener.updateTrackingProtectionPolicy(
+ EngineSharedPreferencesListener.ChangeSource.SETTINGS.source,
+ EngineSharedPreferencesListener.TrackerChanged.ADVERTISING.tracker,
+ settings.shouldBlockAdTrackers(),
+ )
+
+ resources.getString(R.string.pref_key_privacy_block_analytics) ->
+ engineSharedPreferencesListener.updateTrackingProtectionPolicy(
+ EngineSharedPreferencesListener.ChangeSource.SETTINGS.source,
+ EngineSharedPreferencesListener.TrackerChanged.ANALYTICS.tracker,
+ settings.shouldBlockAnalyticTrackers(),
+ )
+
+ resources.getString(R.string.pref_key_privacy_block_other3) ->
+ engineSharedPreferencesListener.updateTrackingProtectionPolicy(
+ EngineSharedPreferencesListener.ChangeSource.SETTINGS.source,
+ EngineSharedPreferencesListener.TrackerChanged.CONTENT.tracker,
+ settings.shouldBlockOtherTrackers(),
+ )
+ resources.getString(R.string.pref_key_cookie_banner_settings) -> {
+ CookieBanner.visitedSetting.record(NoExtras())
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(
+ page = Screen.Settings.Page.CookieBanner,
+ ),
+ )
+ }
+ resources.getString(R.string.pref_key_site_permissions) ->
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.SitePermissions),
+ )
+ resources.getString(R.string.pref_key_studies) ->
+ requireComponents.appStore.dispatch(
+ AppAction.OpenSettings(page = Screen.Settings.Page.Studies),
+ )
+ }
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ private fun updateStealthToggleAvailability() {
+ val switch =
+ preferenceScreen.findPreference(resources.getString(R.string.pref_key_secure)) as? SwitchPreferenceCompat
+
+ val sharedPreferences = preferenceManager.sharedPreferences
+
+ if (sharedPreferences?.getBoolean(
+ resources.getString(R.string.pref_key_biometric),
+ false,
+ ) == true
+ ) {
+ sharedPreferences
+ .edit()
+ .putBoolean(resources.getString(R.string.pref_key_secure), true)
+ .apply()
+
+ // Disable the stealth switch
+ switch?.isChecked = true
+ switch?.isEnabled = false
+ } else {
+ // Enable the stealth switch
+ switch?.isEnabled = true
+ }
+ }
+
+ companion object {
+ const val FRAGMENT_TAG = "PrivacySecuritySettings"
+
+ fun newInstance(): PrivacySecuritySettingsFragment {
+ return PrivacySecuritySettingsFragment()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/TrackingProtectionPanel.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/TrackingProtectionPanel.kt
new file mode 100644
index 0000000000..d7ae7cd8f1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/TrackingProtectionPanel.kt
@@ -0,0 +1,215 @@
+/* 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.focus.settings.privacy
+
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.view.isVisible
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.SavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import mozilla.components.browser.icons.IconRequest
+import mozilla.components.lib.state.ext.observeAsComposableState
+import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
+import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
+import org.mozilla.focus.R
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerItem
+import org.mozilla.focus.cookiebannerreducer.CookieBannerReducerStore
+import org.mozilla.focus.databinding.DialogTrackingProtectionSheetBinding
+import org.mozilla.focus.engine.EngineSharedPreferencesListener.TrackerChanged
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.installedDate
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.ui.theme.FocusTheme
+
+@SuppressWarnings("LongParameterList")
+class TrackingProtectionPanel(
+ context: Context,
+ private val lifecycleOwner: LifecycleOwner,
+ private val cookieBannerReducerStore: CookieBannerReducerStore,
+ private val tabUrl: String,
+ private val blockedTrackersCount: Int,
+ private val isTrackingProtectionOn: Boolean,
+ private val isConnectionSecure: Boolean,
+ private val toggleTrackingProtection: (Boolean) -> Unit,
+ private val updateTrackingProtectionPolicy: (String?, Boolean) -> Unit,
+ private val showConnectionInfo: () -> Unit,
+ private val showCookieBannerExceptionsDetailsPanel: () -> Unit,
+) : BottomSheetDialog(context) {
+
+ private var binding: DialogTrackingProtectionSheetBinding =
+ DialogTrackingProtectionSheetBinding.inflate(layoutInflater, null, false)
+
+ init {
+ initWindow()
+ setContentView(binding.root)
+ expand()
+ updateTitle()
+ updateConnectionState()
+ updateTrackingProtection()
+ updateTrackersBlocked()
+ updateTrackersState()
+ updateCookieBannerException()
+ setListeners()
+ }
+
+ private fun initWindow() {
+ this.window?.decorView?.let {
+ it.setViewTreeLifecycleOwner(lifecycleOwner)
+ it.setViewTreeSavedStateRegistryOwner(
+ lifecycleOwner as SavedStateRegistryOwner,
+ )
+ }
+ }
+
+ private fun expand() {
+ val bottomSheet =
+ findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout
+ BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
+ }
+
+ private fun updateTitle() {
+ binding.siteTitle.text = tabUrl.tryGetHostFromUrl()
+ context.components.icons.loadIntoView(
+ binding.siteFavicon,
+ IconRequest(tabUrl, isPrivate = true),
+ )
+ }
+
+ private fun updateCookieBannerException() {
+ binding.cookieBannerException.apply {
+ setContent {
+ FocusTheme {
+ val cookieBannerExceptionStatus =
+ cookieBannerReducerStore.observeAsComposableState { state ->
+ state.cookieBannerReducerStatus
+ }.value
+ val shouldShowCookieBannerItem =
+ cookieBannerReducerStore.observeAsComposableState { state ->
+ state.shouldShowCookieBannerItem
+ }.value
+ if (shouldShowCookieBannerItem == true) {
+ binding.cookieBannerException.visibility = View.VISIBLE
+ } else {
+ binding.cookieBannerException.visibility = View.GONE
+ }
+
+ if (cookieBannerExceptionStatus != null) {
+ CookieBannerReducerItem(
+ cookieBannerReducerStatus = cookieBannerExceptionStatus,
+ preferenceOnClickListener = ::showCookieBannerExceptionsDetailsPanel.invoke(),
+ )
+ }
+ }
+ }
+ isTransitionGroup = true
+ }
+ }
+
+ private fun updateConnectionState() {
+ binding.securityInfo.text = if (isConnectionSecure) {
+ context.getString(R.string.secure_connection)
+ } else {
+ context.getString(R.string.insecure_connection)
+ }
+
+ val nextIcon = AppCompatResources.getDrawable(context, R.drawable.mozac_ic_chevron_right_24)
+
+ val securityIcon = if (isConnectionSecure) {
+ AppCompatResources.getDrawable(context, R.drawable.mozac_ic_lock_24)
+ } else {
+ AppCompatResources.getDrawable(context, R.drawable.mozac_ic_warning_fill_24)
+ }
+
+ binding.securityInfo.putCompoundDrawablesRelativeWithIntrinsicBounds(
+ start = securityIcon,
+ end = nextIcon,
+ top = null,
+ bottom = null,
+ )
+ }
+
+ private fun updateTrackingProtection() {
+ val description = if (isTrackingProtectionOn) {
+ context.getString(R.string.enhanced_tracking_protection_state_on)
+ } else {
+ context.getString(R.string.enhanced_tracking_protection_state_off)
+ }
+
+ val icon = if (isTrackingProtectionOn) {
+ R.drawable.mozac_ic_shield_24
+ } else {
+ R.drawable.mozac_ic_shield_slash_24
+ }
+
+ val iconContentDescription = context.getString(R.string.enhanced_tracking_protection)
+ binding.enhancedTracking.apply {
+ updateDescription(description)
+ updateIcon(icon = icon, iconContentDescription = iconContentDescription)
+ binding.switchWidget.isChecked = isTrackingProtectionOn
+ }
+ }
+
+ private fun updateTrackersBlocked() {
+ binding.trackersCount.text = blockedTrackersCount.toString()
+ binding.trackersCountNote.text =
+ context.getString(R.string.trackers_count_note, context.installedDate)
+ }
+
+ private fun updateTrackersState() {
+ val settings = context.settings
+
+ with(binding) {
+ advertising.isVisible = isTrackingProtectionOn
+ analytics.isVisible = isTrackingProtectionOn
+ social.isVisible = isTrackingProtectionOn
+ content.isVisible = isTrackingProtectionOn
+ trackersAndScriptsHeading.isVisible = isTrackingProtectionOn
+
+ advertising.isChecked = settings.shouldBlockAdTrackers()
+ analytics.isChecked = settings.shouldBlockAnalyticTrackers()
+ social.isChecked = settings.shouldBlockSocialTrackers()
+ content.isChecked = settings.shouldBlockOtherTrackers()
+ }
+ }
+
+ private fun setListeners() {
+ with(binding) {
+ enhancedTracking.binding.switchWidget.setOnCheckedChangeListener { _, isChecked ->
+ toggleTrackingProtection.invoke(isChecked)
+ dismiss()
+ }
+ advertising.onClickListener {
+ updateTrackingProtectionPolicy(
+ TrackerChanged.ADVERTISING.tracker,
+ advertising.isChecked,
+ )
+ }
+
+ analytics.onClickListener {
+ updateTrackingProtectionPolicy(
+ TrackerChanged.ANALYTICS.tracker,
+ analytics.isChecked,
+ )
+ }
+
+ social.onClickListener {
+ updateTrackingProtectionPolicy(TrackerChanged.SOCIAL.tracker, social.isChecked)
+ }
+
+ content.onClickListener {
+ updateTrackingProtectionPolicy(TrackerChanged.CONTENT.tracker, content.isChecked)
+ }
+
+ securityInfo.setOnClickListener {
+ showConnectionInfo.invoke()
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesAdapter.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesAdapter.kt
new file mode 100644
index 0000000000..57c559bd61
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesAdapter.kt
@@ -0,0 +1,67 @@
+/* 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.focus.settings.privacy.studies
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+
+class StudiesAdapter(var removeStudyListener: (StudiesListItem.ActiveStudy) -> Unit = {}) :
+ ListAdapter(StudiesDiffCallback()) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudiesViewHolder {
+ return when (viewType) {
+ ACTIVE_STUDY_LAYOUT_ID -> {
+ val rootView = LayoutInflater.from(parent.context)
+ .inflate(ACTIVE_STUDY_LAYOUT_ID, parent, false) as ConstraintLayout
+ StudiesViewHolder.ActiveStudiesViewHolder(rootView)
+ }
+ SECTION_LAYOUT_ID -> {
+ val rootView = LayoutInflater.from(parent.context)
+ .inflate(SECTION_LAYOUT_ID, parent, false) as LinearLayout
+ StudiesViewHolder.SectionViewHolder(rootView)
+ }
+ else -> throw IllegalArgumentException("Unrecognized viewType")
+ }
+ }
+
+ override fun onBindViewHolder(holder: StudiesViewHolder, position: Int) {
+ val item = getItem(position)
+
+ when (holder) {
+ is StudiesViewHolder.SectionViewHolder -> holder.bindSection(item as StudiesListItem.Section)
+ is StudiesViewHolder.ActiveStudiesViewHolder -> holder.bindStudy(
+ item as StudiesListItem.ActiveStudy,
+ removeStudyListener,
+ )
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (getItem(position)) {
+ is StudiesListItem.Section -> SECTION_LAYOUT_ID
+ is StudiesListItem.ActiveStudy -> ACTIVE_STUDY_LAYOUT_ID
+ }
+ }
+
+ class StudiesDiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: StudiesListItem, newItem: StudiesListItem): Boolean {
+ return when {
+ oldItem is StudiesListItem.ActiveStudy && newItem is StudiesListItem.ActiveStudy -> {
+ oldItem.value.slug == newItem.value.slug
+ }
+ oldItem is StudiesListItem.Section && newItem is StudiesListItem.Section -> {
+ oldItem.titleId == newItem.titleId
+ }
+ else -> false
+ }
+ }
+
+ override fun areContentsTheSame(oldItem: StudiesListItem, newItem: StudiesListItem) =
+ oldItem == newItem
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesFragment.kt
new file mode 100644
index 0000000000..4753478657
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesFragment.kt
@@ -0,0 +1,158 @@
+/* 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.focus.settings.privacy.studies
+
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.text.style.URLSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
+import androidx.core.text.HtmlCompat
+import androidx.core.text.getSpans
+import androidx.lifecycle.ViewModelProvider
+import mozilla.components.browser.state.state.SessionState
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.FragmentStudiesBinding
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.showToolbar
+import org.mozilla.focus.settings.BaseSettingsLikeFragment
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.utils.SupportUtils
+import kotlin.system.exitProcess
+
+class StudiesFragment : BaseSettingsLikeFragment() {
+ private var _binding: FragmentStudiesBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var viewModel: StudiesViewModel
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentStudiesBinding.inflate(inflater, container, false)
+ viewModel = ViewModelProvider(
+ this,
+ ).get(StudiesViewModel::class.java)
+ setLearnMore()
+ setStudiesSwitch()
+ setRemoveStudyListener()
+ setObservers()
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ showToolbar(getString(R.string.preference_studies))
+ }
+
+ private fun setLearnMore() {
+ val sumoUrl =
+ SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.STUDIES)
+ val description = getString(R.string.preference_studies_summary)
+ val learnMore = getString(R.string.studies_learn_more)
+ val rawText = "$description $learnMore "
+ val text = HtmlCompat.fromHtml(rawText, HtmlCompat.FROM_HTML_MODE_COMPACT)
+
+ val spannableStringBuilder = SpannableStringBuilder(text)
+ val links = spannableStringBuilder.getSpans()
+ for (link in links) {
+ addActionToLinks(spannableStringBuilder, link)
+ }
+ binding.studiesDescription.text = spannableStringBuilder
+ binding.studiesDescription.movementMethod = LinkMovementMethod.getInstance()
+ }
+
+ private fun addActionToLinks(spannableStringBuilder: SpannableStringBuilder, link: URLSpan) {
+ val start = spannableStringBuilder.getSpanStart(link)
+ val end = spannableStringBuilder.getSpanEnd(link)
+ val flags = spannableStringBuilder.getSpanFlags(link)
+ val clickable: ClickableSpan = object : ClickableSpan() {
+ override fun onClick(view: View) {
+ view.setOnClickListener {
+ openLearnMore.invoke()
+ }
+ }
+ }
+ spannableStringBuilder.setSpan(clickable, start, end, flags)
+ spannableStringBuilder.removeSpan(link)
+ }
+
+ private val openLearnMore = {
+ val tabId = requireContext().components.tabsUseCases.addTab(
+ url = SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.STUDIES),
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ requireContext().components.appStore.dispatch(AppAction.OpenTab(tabId))
+ }
+
+ private fun setStudiesTitleByState(switchState: Boolean) {
+ binding.studiesTitle.text = if (switchState) {
+ getString(R.string.preference_state_on)
+ } else {
+ getString(R.string.preference_state_off)
+ }
+ }
+
+ private fun setStudiesSwitch() {
+ binding.studiesSwitch.setOnClickListener {
+ val builder = AlertDialog.Builder(requireContext())
+ .setPositiveButton(
+ R.string.action_ok,
+ ) { dialog, _ ->
+ viewModel.setStudiesState(binding.studiesSwitch.isChecked)
+ dialog.dismiss()
+ quitTheApp()
+ }
+ .setNegativeButton(
+ R.string.action_cancel,
+ ) { dialog, _ ->
+ binding.studiesSwitch.isChecked = !binding.studiesSwitch.isChecked
+ setStudiesTitleByState(binding.studiesSwitch.isChecked)
+ dialog.dismiss()
+ }
+ .setTitle(R.string.preference_studies)
+ .setMessage(R.string.studies_restart_app)
+ .setCancelable(false)
+ val alertDialog: AlertDialog = builder.create()
+ alertDialog.show()
+ }
+ }
+
+ private fun quitTheApp() {
+ exitProcess(0)
+ }
+
+ private fun setRemoveStudyListener() {
+ binding.studiesList.studiesAdapter.removeStudyListener = { study ->
+ viewModel.removeStudy(study)
+ }
+ }
+
+ private fun setObservers() {
+ viewModel.exposedStudies.observe(
+ viewLifecycleOwner,
+ ) { studies ->
+ binding.studiesList.studiesAdapter.submitList(studies)
+ }
+
+ viewModel.studiesState.observe(
+ viewLifecycleOwner,
+ ) { state ->
+ binding.studiesSwitch.isChecked = state
+ setStudiesTitleByState(state)
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesListItem.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesListItem.kt
new file mode 100644
index 0000000000..f9a25754d3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesListItem.kt
@@ -0,0 +1,16 @@
+/* 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.focus.settings.privacy.studies
+
+import androidx.annotation.StringRes
+import org.mozilla.experiments.nimbus.EnrolledExperiment
+
+sealed class StudiesListItem {
+ data class Section(
+ @StringRes val titleId: Int,
+ val visibleDivider: Boolean = true,
+ ) : StudiesListItem()
+
+ data class ActiveStudy(val value: EnrolledExperiment) : StudiesListItem()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesRecyclerView.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesRecyclerView.kt
new file mode 100644
index 0000000000..7b6b2d700e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesRecyclerView.kt
@@ -0,0 +1,21 @@
+/* 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.focus.settings.privacy.studies
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class StudiesRecyclerView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : RecyclerView(context, attrs) {
+ val studiesAdapter: StudiesAdapter = StudiesAdapter()
+
+ init {
+ layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ adapter = studiesAdapter
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewHolder.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewHolder.kt
new file mode 100644
index 0000000000..1fa8baaf3b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewHolder.kt
@@ -0,0 +1,74 @@
+/* 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.focus.settings.privacy.studies
+
+import android.content.Context
+import android.content.DialogInterface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.appcompat.app.AlertDialog
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.textview.MaterialTextView
+import org.mozilla.focus.R
+
+const val SECTION_LAYOUT_ID = R.layout.studies_section_item
+const val ACTIVE_STUDY_LAYOUT_ID = R.layout.active_study_item
+sealed class StudiesViewHolder(var viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup) {
+
+ class ActiveStudiesViewHolder(rootView: ConstraintLayout) :
+ StudiesViewHolder(viewGroup = rootView) {
+ private val titleView = rootView.findViewById(R.id.title)
+ private val summaryView = rootView.findViewById(R.id.summary)
+ private val removeButton = rootView.findViewById(R.id.remove)
+
+ fun bindStudy(
+ activeStudy: StudiesListItem.ActiveStudy,
+ removeStudyListener: (StudiesListItem.ActiveStudy) -> Unit,
+ ) {
+ titleView.text = activeStudy.value.userFacingName
+ summaryView.text = activeStudy.value.userFacingDescription
+ removeButton.setOnClickListener { view ->
+ showRemoveDialog(view.context, removeStudyListener, activeStudy)
+ }
+ }
+
+ private fun showRemoveDialog(
+ context: Context,
+ studyListener: (StudiesListItem.ActiveStudy) -> Unit,
+ activeStudy: StudiesListItem.ActiveStudy,
+ ): AlertDialog {
+ val builder = AlertDialog.Builder(context)
+ .setPositiveButton(
+ R.string.action_ok,
+ ) { dialog, _ ->
+ studyListener.invoke(activeStudy)
+ dialog.dismiss()
+ }
+ .setNegativeButton(
+ R.string.action_cancel,
+ ) { dialog: DialogInterface, _ ->
+ dialog.dismiss()
+ }
+ .setTitle(R.string.preference_studies)
+ .setMessage(R.string.studies_restart_app)
+ .setCancelable(false)
+ val alertDialog: AlertDialog = builder.create()
+ alertDialog.show()
+ return alertDialog
+ }
+ }
+
+ class SectionViewHolder(rootView: LinearLayout) : StudiesViewHolder(viewGroup = rootView) {
+ private val titleView = rootView.findViewById(R.id.title)
+ private val dividerView = rootView.findViewById(R.id.divider)
+
+ fun bindSection(section: StudiesListItem.Section) {
+ titleView.text = titleView.context.resources.getString(section.titleId)
+ dividerView.isVisible = section.visibleDivider
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewModel.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewModel.kt
new file mode 100644
index 0000000000..7a0c293dc0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/privacy/studies/StudiesViewModel.kt
@@ -0,0 +1,65 @@
+/* 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.focus.settings.privacy.studies
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.mozilla.experiments.nimbus.NimbusInterface
+import org.mozilla.experiments.nimbus.internal.EnrolledExperiment
+import org.mozilla.focus.Components
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import kotlin.system.exitProcess
+
+class StudiesViewModel(application: Application) : AndroidViewModel(application) {
+ private var localStudies: MutableList = mutableListOf()
+ val exposedStudies: MutableLiveData> = MutableLiveData()
+ val studiesState: MutableLiveData = MutableLiveData()
+ private val appContext = application
+
+ init {
+ getExperiments(application.components)
+ setStudiesState(application.settings.isExperimentationEnabled)
+ }
+
+ private fun getExperiments(components: Components) {
+ viewModelScope.launch(Dispatchers.IO) {
+ localStudies.add(StudiesListItem.Section(R.string.studies_active, true))
+
+ components.experiments.getActiveExperiments().map { activeExperiment ->
+ localStudies.add(StudiesListItem.ActiveStudy(activeExperiment))
+ }
+ localStudies.add(StudiesListItem.Section(R.string.studies_completed, true))
+ exposedStudies.postValue(localStudies)
+ }
+ }
+
+ fun setStudiesState(state: Boolean) {
+ appContext.settings.isExperimentationEnabled = state
+ appContext.components.experiments.globalUserParticipation = state
+ studiesState.postValue(state)
+ }
+
+ fun removeStudy(study: StudiesListItem.ActiveStudy) {
+ viewModelScope.launch(Dispatchers.IO) {
+ localStudies.remove(study)
+ exposedStudies.postValue(localStudies)
+ appContext.components.experiments.register(
+ object : NimbusInterface.Observer {
+ override fun onUpdatesApplied(updated: List) {
+ // Wait until the experiment is unrolled from nimbus to restart the app
+ exitProcess(0)
+ }
+ },
+ )
+ appContext.components.experiments.optOut(study.value.slug)
+ appContext.components.experiments.applyPendingExperiments()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/HomeScreen.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/HomeScreen.kt
new file mode 100644
index 0000000000..6eb105086a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/HomeScreen.kt
@@ -0,0 +1,122 @@
+/* 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.focus.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Build
+import android.text.TextUtils
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import mozilla.components.support.ktx.kotlin.stripCommonSubdomains
+import org.mozilla.focus.activity.MainActivity
+import java.util.UUID
+
+/**
+ * Helper methods for adding shortcuts to device's home screen.
+ */
+object HomeScreen {
+ const val ADD_TO_HOMESCREEN_TAG = "add_to_homescreen"
+ private const val BLOCKING_ENABLED = "blocking_enabled"
+ const val REQUEST_DESKTOP = "request_desktop"
+
+ /**
+ * Create a shortcut for the given website on the device's home screen.
+ */
+ @Suppress("LongParameterList")
+ fun installShortCut(
+ context: Context,
+ icon: Bitmap,
+ url: String,
+ title: String,
+ blockingEnabled: Boolean,
+ requestDesktop: Boolean,
+ ) {
+ val shortcutTitle = if (TextUtils.isEmpty(title.trim { it <= ' ' })) {
+ generateTitleFromUrl(url)
+ } else {
+ title
+ }
+
+ installShortCutViaManager(context, icon, url, shortcutTitle, blockingEnabled, requestDesktop)
+
+ // Creating shortcut flow is different on Android up to 7, so we want to go
+ // to the home screen manually where the user will see the new shortcut appear
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
+ goToHomeScreen(context)
+ }
+ }
+
+ /**
+ * Create a shortcut via the [ShortcutManagerCompat].
+ *
+ * On Android versions up to 7 shortcut will be created via system broadcast internally.
+ *
+ * On Android 8+ the user will have the ability to add the shortcut manually
+ * or let the system place it automatically.
+ */
+ @Suppress("LongParameterList")
+ private fun installShortCutViaManager(
+ context: Context,
+ bitmap: Bitmap,
+ url: String,
+ title: String,
+ blockingEnabled: Boolean,
+ requestDesktop: Boolean,
+ ) {
+ if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
+ val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ IconCompat.createWithAdaptiveBitmap(bitmap)
+ } else {
+ IconCompat.createWithBitmap(bitmap)
+ }
+ val shortcut = ShortcutInfoCompat.Builder(context, UUID.randomUUID().toString())
+ .setShortLabel(title)
+ .setLongLabel(title)
+ .setIcon(icon)
+ .setIntent(createShortcutIntent(context, url, blockingEnabled, requestDesktop))
+ .build()
+ ShortcutManagerCompat.requestPinShortcut(context, shortcut, null)
+ }
+ }
+
+ private fun createShortcutIntent(
+ context: Context,
+ url: String,
+ blockingEnabled: Boolean,
+ requestDesktop: Boolean,
+ ): Intent {
+ val shortcutIntent = Intent(context, MainActivity::class.java)
+ shortcutIntent.action = Intent.ACTION_VIEW
+ shortcutIntent.data = Uri.parse(url)
+ shortcutIntent.putExtra(BLOCKING_ENABLED, blockingEnabled)
+ shortcutIntent.putExtra(REQUEST_DESKTOP, requestDesktop)
+ shortcutIntent.putExtra(ADD_TO_HOMESCREEN_TAG, ADD_TO_HOMESCREEN_TAG)
+ return shortcutIntent
+ }
+
+ /**
+ * Generates a new default title based on the URL.
+ */
+ @VisibleForTesting
+ fun generateTitleFromUrl(url: String): String {
+ // For now we just use the host name and strip common subdomains like "www" or "m".
+ return Uri.parse(url).host?.stripCommonSubdomains() ?: ""
+ }
+
+ /**
+ * Switch to the the default home screen activity (launcher).
+ */
+ private fun goToHomeScreen(context: Context) {
+ val intent = Intent(Intent.ACTION_MAIN)
+ intent.addCategory(Intent.CATEGORY_HOME)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/IconGenerator.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/IconGenerator.kt
new file mode 100644
index 0000000000..dcfddc6391
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/shortcut/IconGenerator.kt
@@ -0,0 +1,135 @@
+/* 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.focus.shortcut
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Build
+import android.util.TypedValue
+import androidx.core.content.ContextCompat
+import androidx.core.net.toUri
+import mozilla.components.support.ktx.kotlin.stripCommonSubdomains
+import org.mozilla.focus.R
+
+class IconGenerator {
+
+ companion object {
+ private val TEXT_SIZE_DP = 36f
+ private val DEFAULT_ICON_CHAR = '?'
+
+ /**
+ * See [generateAdaptiveLauncherIcon] for more details.
+ */
+ @JvmStatic
+ fun generateLauncherIcon(context: Context, url: String?): Bitmap {
+ val startingChar = getRepresentativeCharacter(url)
+ return generateCharacterIcon(context, startingChar)
+ }
+
+ /**
+ * Generate an icon with the given character. The icon will be drawn
+ * on top of a generic launcher icon shape that we provide.
+ */
+ private fun generateCharacterIcon(context: Context, character: Char) =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ generateAdaptiveLauncherIcon(context, character)
+ } else {
+ generateLauncherIconPreOreo(context, character)
+ }
+
+ /*
+ * This method needs to be separate from generateAdaptiveLauncherIcon so that we can generate
+ * the pre-Oreo icon to display in the Add To Home screen Dialog
+ */
+ @JvmStatic
+ fun generateLauncherIconPreOreo(context: Context, character: Char): Bitmap {
+ val options = BitmapFactory.Options()
+ options.inMutable = true
+ val shape = BitmapFactory.decodeResource(context.resources, R.drawable.ic_homescreen_shape, options)
+ return drawCharacterOnBitmap(context, character, shape)
+ }
+
+ /**
+ * Generates a launcher icon for versions of Android that support Adaptive Icons (Oreo+):
+ * https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive.html
+ */
+ private fun generateAdaptiveLauncherIcon(context: Context, character: Char): Bitmap {
+ val res = context.resources
+ val adaptiveIconDimen = res.getDimensionPixelSize(R.dimen.adaptive_icon_drawable_dimen)
+
+ val bitmap = Bitmap.createBitmap(adaptiveIconDimen, adaptiveIconDimen, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+
+ // Adaptive Icons have two layers: a background that fills the canvas and
+ // a foreground that's centered. First, we draw the background...
+ canvas.drawColor(ContextCompat.getColor(context, R.color.add_to_homescreen_icon_background))
+
+ // Then draw the foreground
+ return drawCharacterOnBitmap(context, character, bitmap)
+ }
+
+ private fun drawCharacterOnBitmap(context: Context, character: Char, bitmap: Bitmap): Bitmap {
+ val canvas = Canvas(bitmap)
+
+ val paint = Paint()
+
+ val textSize = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ TEXT_SIZE_DP,
+ context.resources.displayMetrics,
+ )
+
+ paint.color = Color.WHITE
+ paint.textAlign = Paint.Align.CENTER
+ paint.textSize = textSize
+ paint.isAntiAlias = true
+
+ canvas.drawText(
+ character.toString(),
+ canvas.width / 2.0f,
+ canvas.height / 2.0f - (paint.descent() + paint.ascent()) / 2.0f,
+ paint,
+ )
+
+ return bitmap
+ }
+
+ /**
+ * Get a representative character for the given URL.
+ *
+ * For example this method will return "f" for "http://m.facebook.com/foobar".
+ */
+ @JvmStatic
+ fun getRepresentativeCharacter(url: String?): Char {
+ val firstChar = getRepresentativeSnippet(url)?.find { it.isLetterOrDigit() }?.uppercaseChar()
+ return (firstChar ?: DEFAULT_ICON_CHAR)
+ }
+
+ /**
+ * Get the representative part of the URL. Usually this is the host (without common prefixes).
+ *
+ * @return the representative snippet or null if one could not be found.
+ */
+ private fun getRepresentativeSnippet(url: String?): String? {
+ if (url == null || url.isEmpty()) return null
+
+ val uri = url.toUri()
+ val snippet = if (!uri.host.isNullOrEmpty()) {
+ uri.host // cached by Uri class.
+ } else if (!uri.path.isNullOrEmpty()) { // The uri may not have a host for e.g. file:// uri
+ uri.path // cached by Uri class.
+ } else {
+ return null
+ }
+
+ // Strip common prefixes that we do not want to use to determine the representative characters
+ return snippet?.stripCommonSubdomains()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppAction.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppAction.kt
new file mode 100644
index 0000000000..ca932e3d93
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppAction.kt
@@ -0,0 +1,124 @@
+/* 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.focus.state
+
+import android.os.Bundle
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.lib.state.Action
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+
+/**
+ * An [Action] to be dispatched on the [AppStore].
+ */
+sealed class AppAction : Action {
+ /**
+ * The selected tab has changed.
+ */
+ data class SelectionChanged(
+ val tabId: String,
+ ) : AppAction()
+
+ /**
+ * Action for editing the URL of the tab with the given [tabId].
+ */
+ data class EditAction(
+ val tabId: String,
+ ) : AppAction()
+
+ /**
+ * All tabs have been removed.
+ */
+ object NoTabs : AppAction()
+
+ /**
+ * The user finished editing the URL of the tab with the given [tabId].
+ */
+ data class FinishEdit(
+ val tabId: String,
+ ) : AppAction()
+
+ /**
+ * Hide the tabs tray.
+ */
+ object HideTabs : AppAction()
+
+ /**
+ * The user finished the first run onboarding.
+ */
+ data class FinishFirstRun(val tabId: String?) : AppAction()
+
+ /**
+ * The app should get locked.
+ */
+ data class Lock(val bundle: Bundle? = null) : AppAction()
+
+ /**
+ * The app should get unlocked.
+ */
+ data class Unlock(val tabId: String?) : AppAction()
+
+ data class OpenSettings(val page: Screen.Settings.Page) : AppAction()
+
+ data class OpenSitePermissionOptionsScreen(val sitePermission: SitePermission) : AppAction()
+
+ data class NavigateUp(val tabId: String?) : AppAction()
+
+ /**
+ * Forces showing the first run screen.
+ */
+ internal object ShowFirstRun : AppAction()
+
+ internal object ShowOnboardingSecondScreen : AppAction()
+
+ /**
+ * Forces showing the home screen.
+ */
+ internal object ShowHomeScreen : AppAction()
+
+ /**
+ * Opens the tab with the given [tabId] and actively switches to the browser screen if needed.
+ */
+ data class OpenTab(val tabId: String) : AppAction()
+
+ /**
+ * The list of [TopSite] has changed.
+ */
+ data class TopSitesChange(val topSites: List) : AppAction()
+
+ /**
+ * Site permissions autoplay rules has changed.
+ */
+ data class SitePermissionOptionChange(val value: Boolean) : AppAction()
+
+ /**
+ * State of secret settings has changed.
+ */
+ data class SecretSettingsStateChange(val enabled: Boolean) : AppAction()
+
+ /**
+ * State of erase tabs CFR has changed
+ */
+ data class ShowEraseTabsCfrChange(val value: Boolean) : AppAction()
+
+ /**
+ * State of show Tracking Protection CFR has changed
+ */
+ data class ShowTrackingProtectionCfrChange(val value: Map) : AppAction()
+
+ /**
+ * State of Snackbar for promote search widget has changed
+ */
+ data class ShowSearchWidgetSnackBar(val value: Boolean) : AppAction()
+
+ /**
+ * State of start browsing CFR has changed
+ */
+ data class ShowStartBrowsingCfrChange(val value: Boolean) : AppAction()
+
+ /**
+ * State of start Cookie Banner CFR has changed
+ */
+ data class ShowCookieBannerCfrChange(val value: Boolean) : AppAction()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppReducer.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppReducer.kt
new file mode 100644
index 0000000000..721d6f6516
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppReducer.kt
@@ -0,0 +1,311 @@
+/* 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/. */
+
+@file:Suppress("TooManyFunctions")
+
+package org.mozilla.focus.state
+
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.lib.state.Reducer
+
+/**
+ * Reducer creating a new [AppState] for dispatched [AppAction]s.
+ */
+@Suppress("ComplexMethod")
+object AppReducer : Reducer {
+ override fun invoke(state: AppState, action: AppAction): AppState {
+ return when (action) {
+ is AppAction.SelectionChanged -> selectionChanged(state, action)
+ is AppAction.NoTabs -> noTabs(state)
+ is AppAction.EditAction -> editAction(state, action)
+ is AppAction.FinishEdit -> finishEditing(state, action)
+ is AppAction.HideTabs -> hideTabs(state)
+ is AppAction.ShowFirstRun -> showFirstRun(state)
+ is AppAction.FinishFirstRun -> finishFirstRun(state, action)
+ is AppAction.Lock -> lock(state, action)
+ is AppAction.Unlock -> unlock(state, action)
+ is AppAction.OpenSettings -> openSettings(state, action)
+ is AppAction.NavigateUp -> navigateUp(state, action)
+ is AppAction.OpenTab -> openTab(state, action)
+ is AppAction.TopSitesChange -> topSitesChanged(state, action)
+ is AppAction.SitePermissionOptionChange -> sitePermissionOptionChanged(state, action)
+ is AppAction.SecretSettingsStateChange -> secretSettingsStateChanged(
+ state,
+ action,
+ )
+ is AppAction.ShowEraseTabsCfrChange -> showEraseTabsCfrChanged(state, action)
+ is AppAction.ShowStartBrowsingCfrChange -> showStartBrowsingCfrChanged(state, action)
+ is AppAction.ShowTrackingProtectionCfrChange -> showTrackingProtectionCfrChanged(
+ state,
+ action,
+ )
+ is AppAction.OpenSitePermissionOptionsScreen -> openSitePermissionOptionsScreen(
+ state,
+ action,
+ )
+ is AppAction.ShowHomeScreen -> showHomeScreen(state)
+ is AppAction.ShowOnboardingSecondScreen -> showOnBoardingSecondScreen(state)
+ is AppAction.ShowSearchWidgetSnackBar -> showSearchWidgetSnackBarChanged(state, action)
+ is AppAction.ShowCookieBannerCfrChange -> showCookieBannerCfrChanged(state, action)
+ }
+ }
+}
+
+/**
+ * The currently selected tab has changed.
+ */
+private fun selectionChanged(state: AppState, action: AppAction.SelectionChanged): AppState {
+ if (state.screen is Screen.FirstRun || state.screen is Screen.Locked) {
+ return state
+ }
+
+ return state.copy(
+ screen = Screen.Browser(tabId = action.tabId, showTabs = false),
+ )
+}
+
+/**
+ * All tabs have been closed.
+ */
+private fun noTabs(state: AppState): AppState {
+ if (state.screen is Screen.Home || state.screen is Screen.FirstRun || state.screen is Screen.Locked) {
+ return state
+ }
+ return state.copy(screen = Screen.Home)
+}
+
+/**
+ * The user wants to edit the URL of a tab.
+ */
+private fun editAction(state: AppState, action: AppAction.EditAction): AppState {
+ return state.copy(
+ screen = Screen.EditUrl(action.tabId),
+ )
+}
+
+/**
+ * The user finished editing the URL.
+ */
+private fun finishEditing(state: AppState, action: AppAction.FinishEdit): AppState {
+ return state.copy(
+ screen = Screen.Browser(tabId = action.tabId, showTabs = false),
+ )
+}
+
+/**
+ * Hide the tabs tray.
+ */
+private fun hideTabs(state: AppState): AppState {
+ return if (state.screen is Screen.Browser) {
+ state.copy(screen = state.screen.copy(showTabs = false))
+ } else {
+ state
+ }
+}
+
+/**
+ * The user finished the first run onboarding.
+ */
+private fun finishFirstRun(state: AppState, action: AppAction.FinishFirstRun): AppState {
+ return if (action.tabId != null) {
+ state.copy(screen = Screen.Browser(action.tabId, showTabs = false))
+ } else {
+ state.copy(screen = Screen.Home)
+ }
+}
+
+/**
+ * Force showing the first run screen (for testing).
+ */
+private fun showFirstRun(state: AppState): AppState {
+ return state.copy(screen = Screen.FirstRun)
+}
+
+private fun showOnBoardingSecondScreen(state: AppState): AppState {
+ return state.copy(screen = Screen.OnboardingSecondScreen)
+}
+
+/**
+ * Force showing the home screen.
+ */
+private fun showHomeScreen(state: AppState): AppState {
+ return state.copy(screen = Screen.Home)
+}
+
+/**
+ * Lock the application.
+ */
+private fun lock(state: AppState, action: AppAction.Lock): AppState {
+ return state.copy(screen = Screen.Locked(action.bundle))
+}
+
+/**
+ * Unlock the application.
+ */
+private fun unlock(state: AppState, action: AppAction.Unlock): AppState {
+ if (state.screen !is Screen.Locked) {
+ return state
+ }
+
+ return if (action.tabId != null) {
+ state.copy(screen = Screen.Browser(action.tabId, showTabs = false))
+ } else {
+ state.copy(screen = Screen.Home)
+ }
+}
+
+private fun openSettings(state: AppState, action: AppAction.OpenSettings): AppState {
+ return state.copy(
+ screen = Screen.Settings(page = action.page),
+ )
+}
+
+private fun openTab(state: AppState, action: AppAction.OpenTab): AppState {
+ return state.copy(
+ screen = Screen.Browser(tabId = action.tabId, showTabs = false),
+ )
+}
+
+/**
+ * The list of [TopSite] has changed.
+ */
+private fun topSitesChanged(state: AppState, action: AppAction.TopSitesChange): AppState {
+ return state.copy(topSites = action.topSites)
+}
+
+/**
+ * The rules of site permissions autoplay has changed.
+ */
+private fun sitePermissionOptionChanged(
+ state: AppState,
+ action: AppAction.SitePermissionOptionChange,
+): AppState {
+ return state.copy(sitePermissionOptionChange = action.value)
+}
+
+/**
+ * The state of secret settings has changed.
+ */
+private fun secretSettingsStateChanged(
+ state: AppState,
+ action: AppAction.SecretSettingsStateChange,
+): AppState {
+ return state.copy(secretSettingsEnabled = action.enabled)
+}
+
+/**
+ * The state of erase tabs CFR changed
+ */
+private fun showEraseTabsCfrChanged(
+ state: AppState,
+ action: AppAction.ShowEraseTabsCfrChange,
+): AppState {
+ return state.copy(showEraseTabsCfr = action.value)
+}
+
+/**
+ * Update whether the start browsing CFR should be shown or not
+ */
+private fun showStartBrowsingCfrChanged(
+ state: AppState,
+ action: AppAction.ShowStartBrowsingCfrChange,
+): AppState {
+ return state.copy(showStartBrowsingTabsCfr = action.value)
+}
+
+/**
+ * The state of search widget snackBar changed
+ */
+private fun showSearchWidgetSnackBarChanged(
+ state: AppState,
+ action: AppAction.ShowSearchWidgetSnackBar,
+): AppState {
+ return state.copy(showSearchWidgetSnackbar = action.value)
+}
+
+/**
+ * The state of tracking protection CFR changed
+ */
+private fun showTrackingProtectionCfrChanged(
+ state: AppState,
+ action: AppAction.ShowTrackingProtectionCfrChange,
+): AppState {
+ return state.copy(showTrackingProtectionCfrForTab = action.value)
+}
+
+/**
+ * The state of cookie banner CFR changed
+ */
+private fun showCookieBannerCfrChanged(
+ state: AppState,
+ action: AppAction.ShowCookieBannerCfrChange,
+): AppState {
+ return state.copy(showCookieBannerCfr = action.value)
+}
+
+private fun openSitePermissionOptionsScreen(
+ state: AppState,
+ action: AppAction.OpenSitePermissionOptionsScreen,
+): AppState {
+ return state.copy(screen = Screen.SitePermissionOptionsScreen(sitePermission = action.sitePermission))
+}
+
+@Suppress("ComplexMethod", "ReturnCount")
+private fun navigateUp(state: AppState, action: AppAction.NavigateUp): AppState {
+ if (state.screen is Screen.Browser) {
+ val screen = if (action.tabId != null) {
+ Screen.Browser(tabId = action.tabId, showTabs = false)
+ } else {
+ Screen.Home
+ }
+ return state.copy(screen = screen)
+ }
+
+ if (state.screen is Screen.SitePermissionOptionsScreen) {
+ return state.copy(screen = Screen.Settings(page = Screen.Settings.Page.SitePermissions))
+ }
+
+ if (state.screen !is Screen.Settings) {
+ return state
+ }
+
+ val screen = when (state.screen.page) {
+ Screen.Settings.Page.Start -> if (action.tabId != null) {
+ Screen.Browser(tabId = action.tabId, showTabs = false)
+ } else {
+ Screen.Home
+ }
+
+ Screen.Settings.Page.General -> Screen.Settings(page = Screen.Settings.Page.Start)
+ Screen.Settings.Page.Privacy -> Screen.Settings(page = Screen.Settings.Page.Start)
+ Screen.Settings.Page.Search -> Screen.Settings(page = Screen.Settings.Page.Start)
+ Screen.Settings.Page.Advanced -> Screen.Settings(page = Screen.Settings.Page.Start)
+ Screen.Settings.Page.Mozilla -> Screen.Settings(page = Screen.Settings.Page.Start)
+
+ Screen.Settings.Page.PrivacyExceptions -> Screen.Settings(page = Screen.Settings.Page.Privacy)
+ Screen.Settings.Page.PrivacyExceptionsRemove -> Screen.Settings(page = Screen.Settings.Page.PrivacyExceptions)
+ Screen.Settings.Page.SitePermissions -> Screen.Settings(page = Screen.Settings.Page.Privacy)
+ Screen.Settings.Page.Studies -> Screen.Settings(page = Screen.Settings.Page.Privacy)
+ Screen.Settings.Page.SecretSettings -> Screen.Settings(page = Screen.Settings.Page.Advanced)
+
+ Screen.Settings.Page.SearchList -> Screen.Settings(page = Screen.Settings.Page.Search)
+ Screen.Settings.Page.SearchRemove -> Screen.Settings(page = Screen.Settings.Page.SearchList)
+ Screen.Settings.Page.SearchAdd -> Screen.Settings(page = Screen.Settings.Page.SearchList)
+ Screen.Settings.Page.SearchAutocomplete -> Screen.Settings(page = Screen.Settings.Page.Search)
+ Screen.Settings.Page.SearchAutocompleteList -> Screen.Settings(page = Screen.Settings.Page.SearchAutocomplete)
+
+ Screen.Settings.Page.SearchAutocompleteAdd -> Screen.Settings(
+ page = Screen.Settings.Page.SearchAutocompleteList,
+ )
+ Screen.Settings.Page.SearchAutocompleteRemove -> Screen.Settings(
+ page = Screen.Settings.Page.SearchAutocompleteList,
+ )
+ Screen.Settings.Page.About -> Screen.Settings(page = Screen.Settings.Page.Mozilla)
+ Screen.Settings.Page.Licenses -> Screen.Settings(page = Screen.Settings.Page.Mozilla)
+ Screen.Settings.Page.Locale -> Screen.Settings(page = Screen.Settings.Page.General)
+ Screen.Settings.Page.CookieBanner -> Screen.Settings(page = Screen.Settings.Page.Privacy)
+ }
+
+ return state.copy(screen = screen)
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppState.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppState.kt
new file mode 100644
index 0000000000..92604fbad0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppState.kt
@@ -0,0 +1,124 @@
+/* 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.focus.state
+
+import android.os.Bundle
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.lib.state.State
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+import java.util.UUID
+
+/**
+ * Global state of the application.
+ *
+ * @property screen The currently displayed screen.
+ * @property topSites The list of [TopSite] to display on the Home screen.
+ * @property sitePermissionOptionChange A flag which reflects the state of site permission rules,
+ * whether they have been updated or not
+ * @property secretSettingsEnabled A flag which reflects the state of debug secret settings
+ * @property showEraseTabsCfr A flag which reflects the state erase tabs CFR
+ * @property showStartBrowsingTabsCfr A flag which reflects the state of start browsing CFR
+ * @property showSearchWidgetSnackbar A flag which reflects the state of search widget snackbar
+ * @property showCookieBannerCfr A flag witch reflects the state of cookie banner CFR
+ */
+data class AppState(
+ val screen: Screen,
+ val topSites: List = emptyList(),
+ val sitePermissionOptionChange: Boolean = false,
+ val secretSettingsEnabled: Boolean = false,
+ val showEraseTabsCfr: Boolean = false,
+ val showSearchWidgetSnackbar: Boolean = false,
+ val showTrackingProtectionCfrForTab: Map = emptyMap(),
+ val showStartBrowsingTabsCfr: Boolean = false,
+ val showCookieBannerCfr: Boolean = false,
+) : State
+
+/**
+ * A group of screens that the app can display.
+ *
+ * @property id A unique ID for identifying a screen. Only if this ID changes the screen is
+ * considered a new screen that requires a navigation (as opposed to the state of the screen
+ * changing).
+ */
+sealed class Screen {
+ open val id = UUID.randomUUID().toString()
+
+ /**
+ * First run onboarding.
+ */
+ object FirstRun : Screen()
+
+ /**
+ * Second screen from new onboarding flow.
+ */
+ object OnboardingSecondScreen : Screen()
+
+ /**
+ * The home screen.
+ */
+ object Home : Screen()
+
+ /**
+ * Browser screen.
+ *
+ * @property tabId The ID of the displayed tab.
+ * @property showTabs Whether to show the tabs tray.
+ */
+ data class Browser(
+ val tabId: String,
+ val showTabs: Boolean,
+ ) : Screen() {
+ // Whenever the showTabs property changes we want to treat this as a new screen and force
+ // a navigation.
+ override val id: String = "${super.id}_$showTabs}"
+ }
+
+ /**
+ * Editing the URL of a tab.
+ */
+ data class EditUrl(
+ val tabId: String,
+ ) : Screen()
+
+ /**
+ * The application is locked (and requires unlocking).
+ *
+ * @param bundle it is used for app navigation. If the user can unlock with success he should
+ * be redirected to a certain screen.It comes from the external intent.
+ */
+ data class Locked(val bundle: Bundle? = null) : Screen()
+ data class SitePermissionOptionsScreen(val sitePermission: SitePermission) : Screen()
+ data class Settings(
+ val page: Page = Page.Start,
+ ) : Screen() {
+ enum class Page {
+ Start,
+
+ General,
+ Privacy,
+ Search,
+ Advanced,
+ Mozilla,
+ About,
+ Licenses,
+ Locale,
+
+ PrivacyExceptions,
+ PrivacyExceptionsRemove,
+ CookieBanner,
+ SitePermissions,
+ Studies,
+ SecretSettings,
+
+ SearchList,
+ SearchRemove,
+ SearchAdd,
+ SearchAutocomplete,
+ SearchAutocompleteList,
+ SearchAutocompleteAdd,
+ SearchAutocompleteRemove,
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppStore.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppStore.kt
new file mode 100644
index 0000000000..b86cba96da
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/state/AppStore.kt
@@ -0,0 +1,14 @@
+/* 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.focus.state
+
+import mozilla.components.lib.state.Store
+
+/**
+ * A [Store] keeping track of the global app state.
+ */
+class AppStore(
+ initialState: AppState,
+) : Store(initialState, AppReducer)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ActivationPing.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ActivationPing.kt
new file mode 100644
index 0000000000..efeb9579cc
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ActivationPing.kt
@@ -0,0 +1,84 @@
+/* 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.focus.telemetry
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.focus.GleanMetrics.Activation
+import org.mozilla.focus.GleanMetrics.Pings
+
+/**
+ * Ensures that only one activation ping is ever sent.
+ *
+ * (Taken from Fenix)
+ */
+class ActivationPing(private val context: Context) {
+
+ private val prefs: SharedPreferences by lazy {
+ context.getSharedPreferences(
+ "ActivationPing.prefs",
+ Context.MODE_PRIVATE,
+ )
+ }
+
+ /**
+ * Checks whether or not the activation ping was already
+ * triggered by the application.
+ *
+ * Note that this only tells us that Fenix triggered the
+ * ping and then delegated the transmission to Glean. We
+ * have no way to tell if it was actually sent or not.
+ *
+ * @return true if it was already triggered, false otherwise.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun wasAlreadyTriggered(): Boolean {
+ return prefs.getBoolean("ping_sent", false)
+ }
+
+ /**
+ * Marks the "activation" ping as triggered by the application.
+ * This ensures the ping is not triggered again at the next app
+ * start.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun markAsTriggered() {
+ prefs.edit().putBoolean("ping_sent", true).apply()
+ }
+
+ /**
+ * Fills the metrics and triggers the 'activation' ping.
+ * This is a separate function to simplify unit-testing.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun triggerPing() {
+ // Generate the activation_id.
+ Activation.activationId.generateAndSet()
+
+ CoroutineScope(Dispatchers.IO).launch {
+ Pings.activation.submit()
+ markAsTriggered()
+ }
+ }
+
+ /**
+ * Trigger sending the `activation` ping if it wasn't sent already.
+ * Then, mark it so that it doesn't get triggered next time Fenix
+ * starts.
+ */
+ fun checkAndSend() {
+ if (wasAlreadyTriggered()) {
+ Logger.debug("ActivationPing - already generated")
+ return
+ }
+
+ triggerPing()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/BrowsersCache.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/BrowsersCache.kt
new file mode 100644
index 0000000000..1f70b9b679
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/BrowsersCache.kt
@@ -0,0 +1,41 @@
+/* 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.focus.telemetry
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import mozilla.components.support.utils.Browsers
+
+/**
+ * Caches the list of browsers installed on a user's device.
+ *
+ * BrowsersCache caches the list of installed browsers is gathered lazily when it is first accessed
+ * after initial creation or invalidation. For that reason, a context is required every time
+ * the cache is accessed.
+ *
+ * Users are responsible for invalidating the cache at the appropriate time. It is left up to the
+ * user to determine appropriate policies for maintaining the validity of the cache. If, when the
+ * cache is accessed, it is filled, the contents will be returned. As mentioned above, the cache
+ * will be lazily refilled after invalidation. In other words, invalidation is O(1).
+ *
+ * This cache is threadsafe.
+ */
+object BrowsersCache {
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal var cachedBrowsers: Browsers? = null
+
+ @Synchronized
+ fun all(context: Context): Browsers {
+ run {
+ val cachedBrowsers = cachedBrowsers
+ if (cachedBrowsers != null) {
+ return cachedBrowsers
+ }
+ }
+ return Browsers.all(context).also {
+ cachedBrowsers = it
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FactsProcessor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FactsProcessor.kt
new file mode 100644
index 0000000000..c3c60136bb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FactsProcessor.kt
@@ -0,0 +1,82 @@
+/* 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.focus.telemetry
+
+import androidx.annotation.VisibleForTesting
+import mozilla.components.browser.menu.facts.BrowserMenuFacts
+import mozilla.components.feature.contextmenu.facts.ContextMenuFacts
+import mozilla.components.feature.customtabs.CustomTabsFacts
+import mozilla.components.feature.search.telemetry.ads.AdsTelemetry
+import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry
+import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.base.Component
+import mozilla.components.support.base.facts.Fact
+import mozilla.components.support.base.facts.FactProcessor
+import mozilla.components.support.base.facts.Facts
+import org.mozilla.focus.GleanMetrics.Browser
+import org.mozilla.focus.GleanMetrics.BrowserSearch
+import org.mozilla.focus.GleanMetrics.ContextMenu
+import org.mozilla.focus.GleanMetrics.CustomTabsToolbar
+
+/**
+ * Processes all [Fact]s emitted from Android Components based on which the appropriate telemetry
+ * will be collected.
+ */
+object FactsProcessor {
+ fun initialize() {
+ Facts.registerProcessor(
+ object : FactProcessor {
+ override fun process(fact: Fact) {
+ fact.process()
+ }
+ },
+ )
+ }
+
+ @VisibleForTesting
+ internal fun Fact.process() = when (Pair(component, item)) {
+ Component.FEATURE_SEARCH to AdsTelemetry.SERP_ADD_CLICKED -> {
+ BrowserSearch.adClicks[value!!].add()
+ }
+ Component.FEATURE_SEARCH to AdsTelemetry.SERP_SHOWN_WITH_ADDS -> {
+ BrowserSearch.withAds[value!!].add()
+ }
+ Component.FEATURE_SEARCH to InContentTelemetry.IN_CONTENT_SEARCH -> {
+ BrowserSearch.inContent[value!!].add()
+ }
+
+ Component.FEATURE_CUSTOMTABS to CustomTabsFacts.Items.CLOSE -> {
+ CustomTabsToolbar.closeTabTapped.record(NoExtras())
+ }
+
+ Component.FEATURE_CUSTOMTABS to CustomTabsFacts.Items.ACTION_BUTTON -> {
+ CustomTabsToolbar.actionButtonTapped.record(NoExtras())
+ }
+
+ Component.FEATURE_CONTEXTMENU to ContextMenuFacts.Items.ITEM -> {
+ ContextMenu.itemTapped.record(ContextMenu.ItemTappedExtra(toContextMenuExtraKey()))
+ }
+
+ Component.BROWSER_MENU to BrowserMenuFacts.Items.WEB_EXTENSION_MENU_ITEM -> {
+ if (metadata?.get("id") == "webcompat-reporter@mozilla.org") {
+ Browser.reportSiteIssueCounter.add()
+ } else {
+ // other extension action was emitted
+ }
+ }
+
+ else -> Unit
+ }
+}
+
+/**
+ * Extracts an extraKey from a context menu Fact.
+ */
+fun Fact.toContextMenuExtraKey() =
+ if (component == Component.FEATURE_CONTEXTMENU) {
+ metadata?.get("item").toString().removePrefix("mozac.feature.contextmenu.")
+ } else {
+ throw IllegalArgumentException("Fact is not a context menu fact")
+ }
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FenixProductDetector.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FenixProductDetector.kt
new file mode 100644
index 0000000000..2c7de5af7e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/FenixProductDetector.kt
@@ -0,0 +1,49 @@
+/* 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.focus.telemetry
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import mozilla.components.support.utils.ext.getPackageInfoCompat
+
+object FenixProductDetector {
+ enum class FenixVersion(val packageName: String) {
+ FIREFOX("org.mozilla.firefox"),
+ FIREFOX_NIGHTLY("org.mozilla.fenix"),
+ FIREFOX_BETA("org.mozilla.firefox_beta"),
+ }
+
+ fun getInstalledFenixVersions(context: Context): List {
+ val fenixVersions = mutableListOf()
+
+ for (product in FenixVersion.values()) {
+ if (packageIsInstalled(context, product.packageName)) {
+ fenixVersions.add(product.packageName)
+ }
+ }
+
+ return fenixVersions
+ }
+
+ fun isFenixDefaultBrowser(defaultBrowser: ActivityInfo?): Boolean {
+ if (defaultBrowser == null) return false
+
+ for (product in FenixVersion.values()) {
+ if (product.packageName == defaultBrowser.packageName) return true
+ }
+ return false
+ }
+
+ private fun packageIsInstalled(context: Context, packageName: String): Boolean {
+ try {
+ context.packageManager.getPackageInfoCompat(packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ return false
+ }
+
+ return true
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/GleanMetricsService.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/GleanMetricsService.kt
new file mode 100644
index 0000000000..1b6aec961c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/GleanMetricsService.kt
@@ -0,0 +1,243 @@
+/* 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.focus.telemetry
+
+import android.content.Context
+import android.os.Build
+import android.os.RemoteException
+import android.os.StrictMode
+import androidx.annotation.VisibleForTesting
+import androidx.core.app.NotificationManagerCompat
+import androidx.preference.PreferenceManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import mozilla.components.browser.state.search.SearchEngine
+import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
+import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine
+import mozilla.components.feature.search.telemetry.SearchProviderModel
+import mozilla.components.feature.search.telemetry.SerpTelemetryRepository
+import mozilla.components.service.glean.net.ConceptFetchHttpUploader
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.ktx.android.content.res.readJSONObject
+import mozilla.telemetry.glean.Glean
+import mozilla.telemetry.glean.config.Configuration
+import org.mozilla.focus.BuildConfig
+import org.mozilla.focus.Components
+import org.mozilla.focus.GleanMetrics.Browser
+import org.mozilla.focus.GleanMetrics.GleanBuildInfo
+import org.mozilla.focus.GleanMetrics.Metrics
+import org.mozilla.focus.GleanMetrics.MozillaProducts
+import org.mozilla.focus.GleanMetrics.Notifications
+import org.mozilla.focus.GleanMetrics.Pings
+import org.mozilla.focus.GleanMetrics.Preferences
+import org.mozilla.focus.GleanMetrics.Shortcuts
+import org.mozilla.focus.GleanMetrics.TrackingProtection
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.topsites.DefaultTopSitesStorage.Companion.TOP_SITES_MAX_LIMIT
+import org.mozilla.focus.utils.AppConstants
+import org.mozilla.focus.utils.Settings
+
+/**
+ * Glean telemetry service.
+ *
+ * To track events, use Glean's generated bindings directly.
+ */
+class GleanMetricsService(context: Context) : MetricsService {
+
+ @Suppress("UnusedPrivateMember")
+ private val activationPing = ActivationPing(context)
+
+ companion object {
+ // collection name to fetch from server for SERP telemetry
+ const val COLLECTION_NAME = "search-telemetry-v2"
+
+ // urls for prod and stage remote settings server
+ internal const val REMOTE_PROD_ENDPOINT_URL = "https://firefox.settings.services.mozilla.com"
+ internal const val REMOTE_STAGE_ENDPOINT_URL = "https://firefox.settings.services.allizom.org"
+
+ private val isEnabledByDefault: Boolean
+ get() = !AppConstants.isKlarBuild
+
+ private fun isDeviceWithTelemetryDisabled(): Boolean {
+ val brand = "blackberry"
+ val device = "bbf100"
+
+ return Build.BRAND == brand && Build.DEVICE == device
+ }
+
+ /**
+ * Determines whether or not telemetry is enabled.
+ */
+ @JvmStatic
+ fun isTelemetryEnabled(context: Context): Boolean {
+ if (isDeviceWithTelemetryDisabled()) { return false }
+
+ // The first access to shared preferences will require a disk read.
+ val threadPolicy = StrictMode.allowThreadDiskReads()
+ try {
+ val resources = context.resources
+ val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+ return preferences.getBoolean(
+ resources.getString(R.string.pref_key_telemetry),
+ isEnabledByDefault,
+ ) && !AppConstants.isDevBuild
+ } finally {
+ StrictMode.setThreadPolicy(threadPolicy)
+ }
+ }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ override fun initialize(context: Context) {
+ val components = context.components
+ val settings = context.settings
+ val telemetryEnabled = isTelemetryEnabled(context)
+
+ Glean.initialize(
+ applicationContext = context,
+ uploadEnabled = telemetryEnabled,
+ configuration = Configuration(
+ channel = BuildConfig.FLAVOR,
+ httpClient = ConceptFetchHttpUploader(
+ client = lazy(LazyThreadSafetyMode.NONE) { components.client },
+ usePrivateRequest = true,
+ ),
+ ),
+ buildInfo = GleanBuildInfo.buildInfo,
+ )
+
+ Glean.registerPings(Pings)
+
+ if (telemetryEnabled) {
+ CoroutineScope(Dispatchers.Main).launch {
+ val readJson = { context.assets.readJSONObject("search/search_telemetry_v2.json") }
+ val providerList = withContext(Dispatchers.IO) {
+ SerpTelemetryRepository(
+ rootStorageDirectory = context.filesDir,
+ readJson = readJson,
+ collectionName = COLLECTION_NAME,
+ serverUrl = if (context.settings.useProductionRemoteSettingsServer) {
+ REMOTE_PROD_ENDPOINT_URL
+ } else {
+ REMOTE_STAGE_ENDPOINT_URL
+ },
+ ).updateProviderList()
+ }
+ installSearchTelemetryExtensions(components, providerList)
+ }
+ }
+
+ // Do this immediately after init.
+ GlobalScope.launch(IO) {
+ // Wait for preferences to be collected before we send the activation ping.
+ collectPrefMetricsAsync(components, settings, context).await()
+
+ components.store.waitForSelectedOrDefaultSearchEngine { searchEngine ->
+ if (searchEngine != null) {
+ Browser.defaultSearchEngine.set(getDefaultSearchEngineIdentifierForTelemetry(context))
+ }
+
+ activationPing.checkAndSend()
+ }
+ }
+ }
+
+ private fun collectPrefMetricsAsync(
+ components: Components,
+ settings: Settings,
+ context: Context,
+ ) = CoroutineScope(IO).async {
+ val installedBrowsers = BrowsersCache.all(context)
+ val hasFenixInstalled = FenixProductDetector.getInstalledFenixVersions(context).isNotEmpty()
+ val isFenixDefaultBrowser = FenixProductDetector.isFenixDefaultBrowser(installedBrowsers.defaultBrowser)
+ val isFocusDefaultBrowser = installedBrowsers.isDefaultBrowser
+
+ Metrics.searchWidgetInstalled.set(settings.searchWidgetInstalled)
+
+ Browser.isDefault.set(isFocusDefaultBrowser)
+ Browser.localeOverride.set(components.store.state.locale?.displayName ?: "none")
+ val shortcutsOnHomeNumber = components.topSitesStorage.getTopSites(
+ totalSites = TOP_SITES_MAX_LIMIT,
+ frecencyConfig = null,
+ ).size
+ Shortcuts.shortcutsOnHomeNumber.set(shortcutsOnHomeNumber.toLong())
+
+ val installSourcePackage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ context.packageManager.getInstallSourceInfo(context.packageName).installingPackageName
+ } else {
+ @Suppress("DEPRECATION")
+ context.packageManager.getInstallerPackageName(context.packageName)
+ }
+
+ Browser.installSource.set(installSourcePackage.orEmpty())
+
+ // Fenix telemetry
+ MozillaProducts.hasFenixInstalled.set(hasFenixInstalled)
+ MozillaProducts.isFenixDefaultBrowser.set(isFenixDefaultBrowser)
+
+ // tracking protection metrics
+ TrackingProtection.hasAdvertisingBlocked.set(settings.hasAdvertisingBlocked())
+ TrackingProtection.hasAnalyticsBlocked.set(settings.hasAnalyticsBlocked())
+ TrackingProtection.hasContentBlocked.set(settings.shouldBlockOtherTrackers())
+ TrackingProtection.hasSocialBlocked.set(settings.hasSocialBlocked())
+
+ // theme telemetry
+ val currentTheme =
+ when {
+ settings.lightThemeSelected -> {
+ "Light"
+ }
+ settings.darkThemeSelected -> {
+ "Dark"
+ }
+
+ settings.useDefaultThemeSelected -> {
+ "Follow device"
+ }
+ else -> ""
+ }
+ if (currentTheme.isNotEmpty()) {
+ Preferences.userTheme.set(currentTheme)
+ }
+
+ try {
+ Notifications.permissionGranted.set(
+ NotificationManagerCompat.from(context).areNotificationsEnabled(),
+ )
+ } catch (e: RemoteException) {
+ Logger.warn("Failed to check notifications state", e)
+ }
+ }
+
+ private fun getDefaultSearchEngineIdentifierForTelemetry(context: Context): String {
+ val searchEngine = context.components.store.state.search.selectedOrDefaultSearchEngine
+ return if (searchEngine?.type == SearchEngine.Type.CUSTOM) {
+ "custom"
+ } else {
+ searchEngine?.name ?: ""
+ }
+ }
+
+ @VisibleForTesting
+ internal suspend fun installSearchTelemetryExtensions(
+ components: Components,
+ providerList: List,
+ ) {
+ val engine = components.engine
+ components.store.apply {
+ components.adsTelemetry.install(engine, this@apply, providerList)
+ components.searchTelemetry.install(engine, this@apply, providerList)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/MetricsService.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/MetricsService.kt
new file mode 100644
index 0000000000..e43f3e0330
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/MetricsService.kt
@@ -0,0 +1,18 @@
+/* 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.focus.telemetry
+
+import android.content.Context
+
+/**
+ * A service for tracking telemetry events in various parts of the app.
+ */
+interface MetricsService {
+
+ /**
+ * Perform any initialization that the service may require.
+ */
+ fun initialize(context: Context)
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessor.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessor.kt
new file mode 100644
index 0000000000..48980560b1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessor.kt
@@ -0,0 +1,85 @@
+/* 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.focus.telemetry
+
+import android.os.Handler
+import android.os.Looper
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import mozilla.components.concept.base.profiler.Profiler
+import mozilla.components.support.base.facts.Action
+import mozilla.components.support.base.facts.Fact
+import mozilla.components.support.base.facts.FactProcessor
+
+/**
+ * A fact processor that adds Gecko profiler markers for [Fact]s matching a specific format.
+ * We look for the following format:
+ * ```
+ * Fact(
+ * action = Action.IMPLEMENTATION_DETAIL
+ * item =
+ * )
+ * ```
+ *
+ * This allows us to add profiler markers from android-components code. Using the Fact API for this
+ * purpose, rather than calling [Profiler.addMarker] directly inside components, has trade-offs. Its
+ * downsides are that it is less explicit and tooling does not work as well on it. However, we felt
+ * it was worthwhile because:
+ *
+ * 1. we don't know what profiler markers are useful so we want to be able to iterate quickly.
+ * Adding dependencies on the Profiler and landing these changes across two repos hinders that
+ * 2. we want to instrument the code as close to specific method calls as possible (e.g.
+ * GeckoSession.loadUrl) but it's not always easy to do so (e.g. in the previous example, passing a
+ * Profiler reference to GeckoEngineSession is difficult because GES is not a global dependency)
+ * 3. we can only add Profiler markers from the main thread so adding markers will become more
+ * difficult if we have to understand the threading needs of each Profiler call site
+ *
+ * An additional benefit with having this infrastructure is that it's easy to add Profiler markers
+ * for local debugging.
+ *
+ * That being said, if we find a location where it would be valuable to have a long term Profiler
+ * marker, we should consider instrumenting it via the [Profiler] API.
+ */
+class ProfilerMarkerFactProcessor
+@VisibleForTesting(otherwise = PRIVATE)
+constructor(
+ // We use a provider to defer accessing the profiler until we need it, because the property is a
+ // child of the engine property and we don't want to initialize it earlier than we intend to.
+ private val profilerProvider: () -> Profiler?,
+ private val mainHandler: Handler = Handler(Looper.getMainLooper()),
+ private val getMyLooper: () -> Looper? = { Looper.myLooper() },
+) : FactProcessor {
+
+ override fun process(fact: Fact) {
+ if (fact.action != Action.IMPLEMENTATION_DETAIL) {
+ return
+ }
+
+ val markerName = fact.item
+
+ // Java profiler markers can only be added from the main thread so, for now, we push all
+ // markers to the the main thread (which also groups all the markers together,
+ // making it easier to read).
+ val profiler = profilerProvider()
+ if (getMyLooper() == mainHandler.looper) {
+ profiler?.addMarker(markerName)
+ } else {
+ // To reduce the performance burden, we could early return if the profiler isn't active.
+ // However, this would change the performance characteristics from when the profiler is
+ // active and when it's inactive so we always post instead.
+ val now = profiler?.getProfilerTime()
+ mainHandler.post {
+ // We set now to both start and end time because we want a marker of without duration
+ // and if end is omitted, the duration is created implicitly.
+ profiler?.addMarker(markerName, now, now, null)
+ }
+ }
+ }
+
+ companion object {
+ fun create(profilerProvider: () -> Profiler?) =
+ ProfilerMarkerFactProcessor(profilerProvider)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/TelemetryMiddleware.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/TelemetryMiddleware.kt
new file mode 100644
index 0000000000..7f716820eb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/TelemetryMiddleware.kt
@@ -0,0 +1,148 @@
+/* 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.focus.telemetry
+
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.action.CustomTabListAction
+import mozilla.components.browser.state.action.DownloadAction
+import mozilla.components.browser.state.action.TabListAction
+import mozilla.components.browser.state.selector.findTab
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.state.CustomTabConfig
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.browser.state.state.content.DownloadState
+import mozilla.components.concept.engine.window.WindowRequest
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import mozilla.components.service.glean.private.NoExtras
+import org.mozilla.focus.GleanMetrics.AppOpened
+import org.mozilla.focus.GleanMetrics.Browser
+import org.mozilla.focus.GleanMetrics.Downloads
+import org.mozilla.focus.GleanMetrics.TabCount
+import kotlin.collections.forEach as withEach
+
+class TelemetryMiddleware : Middleware {
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ next(action)
+
+ when (action) {
+ is TabListAction.AddTabAction -> {
+ collectTelemetry(action.tab, context)
+ }
+ is TabListAction.AddMultipleTabsAction -> action.tabs.withEach {
+ collectTelemetry(it, context)
+ }
+
+ is CustomTabListAction.TurnCustomTabIntoNormalTabAction -> {
+ TabCount.newTabOpened.record(
+ TabCount.NewTabOpenedExtra(context.state.tabs.size, "custom tab"),
+ )
+ }
+
+ is ContentAction.UpdateLoadingStateAction -> {
+ context.state.findTab(action.sessionId)?.let { tab ->
+ // Record UriOpened event when a page finishes loading
+ if (!tab.content.loading && !action.loading) {
+ Browser.totalUriCount.add()
+ }
+ }
+ }
+ is DownloadAction.AddDownloadAction -> {
+ Downloads.downloadStarted.record(NoExtras())
+ }
+ is DownloadAction.UpdateDownloadAction -> {
+ if (action.download.status == DownloadState.Status.CANCELLED) {
+ Downloads.downloadCanceled.record(NoExtras())
+ }
+ }
+ else -> {
+ // no-op
+ }
+ }
+ }
+
+ private fun collectTelemetry(
+ tab: SessionState,
+ context: MiddlewareContext,
+ ) {
+ val tabCount = context.state.tabs.size
+
+ when (tab.source) {
+ is SessionState.Source.External.ActionView -> {
+ AppOpened.browseIntent.record(NoExtras())
+ }
+ is SessionState.Source.External.ActionSend -> {
+ AppOpened.shareIntent.record(
+ AppOpened.ShareIntentExtra(tab.content.searchTerms.isNotEmpty()),
+ )
+ }
+ SessionState.Source.Internal.TextSelection -> {
+ AppOpened.textSelectionIntent.record(NoExtras())
+ }
+ SessionState.Source.Internal.HomeScreen -> {
+ AppOpened.fromLauncherSiteShortcut.record(NoExtras())
+ }
+ SessionState.Source.Internal.NewTab -> {
+ val parentTab = (tab as TabSessionState).parentId?.let { context.state.findTab(it) }
+ if (parentTab?.content?.windowRequest?.type == WindowRequest.Type.OPEN) {
+ TabCount.newTabOpened.record(
+ TabCount.NewTabOpenedExtra(tabCount, "Window.open()"),
+ )
+ } else {
+ TabCount.newTabOpened.record(
+ TabCount.NewTabOpenedExtra(tabCount, "context menu"),
+ )
+ }
+ }
+
+ else -> {
+ // For other session types we create events at the place where we create the sessions.
+ }
+ }
+ }
+
+ /**
+ * This method creates a list of options used to share with Telemetry and was migrated from A-C.
+ *
+ * @param customTabConfig The customTabConfig to use
+ * @return A list of strings representing the customTabConfig
+ */
+ @Suppress("ComplexMethod")
+ private fun generateOptions(customTabConfig: CustomTabConfig): List {
+ val options = mutableListOf()
+
+ if (customTabConfig.colorSchemes?.defaultColorSchemeParams?.toolbarColor != null) {
+ options.add(TOOLBAR_COLOR_OPTION)
+ }
+ if (customTabConfig.closeButtonIcon != null) options.add(CLOSE_BUTTON_OPTION)
+ if (customTabConfig.enableUrlbarHiding) options.add(DISABLE_URLBAR_HIDING_OPTION)
+ if (customTabConfig.actionButtonConfig != null) options.add(ACTION_BUTTON_OPTION)
+ if (customTabConfig.showShareMenuItem) options.add(SHARE_MENU_ITEM_OPTION)
+ if (customTabConfig.menuItems.isNotEmpty()) options.add(CUSTOMIZED_MENU_OPTION)
+ if (customTabConfig.actionButtonConfig?.tint == true) options.add(ACTION_BUTTON_TINT_OPTION)
+ if (customTabConfig.exitAnimations != null) options.add(EXIT_ANIMATION_OPTION)
+ if (customTabConfig.titleVisible) options.add(PAGE_TITLE_OPTION)
+
+ return options
+ }
+
+ companion object {
+ internal const val TOOLBAR_COLOR_OPTION = "hasToolbarColor"
+ internal const val CLOSE_BUTTON_OPTION = "hasCloseButton"
+ internal const val DISABLE_URLBAR_HIDING_OPTION = "disablesUrlbarHiding"
+ internal const val ACTION_BUTTON_OPTION = "hasActionButton"
+ internal const val SHARE_MENU_ITEM_OPTION = "hasShareItem"
+ internal const val CUSTOMIZED_MENU_OPTION = "hasCustomizedMenu"
+ internal const val ACTION_BUTTON_TINT_OPTION = "hasActionButtonTint"
+ internal const val EXIT_ANIMATION_OPTION = "hasExitAnimation"
+ internal const val PAGE_TITLE_OPTION = "hasPageTitle"
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/AppStartReasonProvider.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/AppStartReasonProvider.kt
new file mode 100644
index 0000000000..c1453e4e75
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/AppStartReasonProvider.kt
@@ -0,0 +1,99 @@
+/* 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.focus.telemetry.startuptelemetry
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.focus.GleanMetrics.Metrics
+import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider.StartReason
+
+private val logger = Logger("AppStartReasonProvider")
+
+/**
+ * Provides the reason this [Application] instance was started: see [StartReason] for options
+ * and [reason] for details.
+ *
+ * This class relies on specific lifecycle method call orders and main thread Runnable scheduling
+ * that could potentially change between OEMs and OS versions: **be careful when using it.** This
+ * implementation was tested on the Moto G5 Android 8.1.0 and the Pixel 2 Android 11.
+ */
+class AppStartReasonProvider {
+
+ enum class StartReason {
+ /** We don't know yet what caused this [Application] instance to be started. */
+ TO_BE_DETERMINED,
+
+ /** This [Application] instance was started due to an Activity trying to start. */
+ ACTIVITY,
+
+ /**
+ * This [Application] instance was started due to a component that is not an Activity:
+ * this may include Services, BroadcastReceivers, and ContentProviders. It may be possible
+ * to distinguish between these but it hasn't been necessary to do so yet.
+ */
+ NON_ACTIVITY,
+ }
+
+ /**
+ * The reason this [Application] instance was started. This will not be set immediately
+ * but is expected to be available by the time the first frame is drawn for the foreground Activity.
+ */
+ var reason = StartReason.TO_BE_DETERMINED
+ private set
+
+ /**
+ * Registers the handlers needed by this class: this is expected to be called from
+ * [Application.onCreate].
+ */
+ fun registerInAppOnCreate(application: Application) {
+ ProcessLifecycleOwner.get().lifecycle.addObserver(ProcessLifecycleObserver())
+ application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks())
+ }
+
+ private inner class ProcessLifecycleObserver : DefaultLifecycleObserver {
+ override fun onCreate(owner: LifecycleOwner) {
+ Handler(Looper.getMainLooper()).post {
+ // If the Application was started by an Activity, this Runnable should execute
+ // after we learn the Activity was created. If the App was started by a Service,
+ // this Runnable should execute before the first Activity is created.
+ reason = when (reason) {
+ StartReason.TO_BE_DETERMINED -> StartReason.NON_ACTIVITY
+ StartReason.ACTIVITY -> reason // the start reason is already known: do nothing.
+ StartReason.NON_ACTIVITY -> {
+ Metrics.startReasonProcessError.set(true)
+ logger.error("AppStartReasonProvider.Process...onCreate unexpectedly called twice")
+ reason
+ }
+ }
+ }
+
+ owner.lifecycle.removeObserver(this) // we don't update the state further.
+ }
+ }
+
+ private inner class ActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ // See ProcessLifecycleObserver.onCreate for details.
+ reason = when (reason) {
+ StartReason.TO_BE_DETERMINED -> StartReason.ACTIVITY
+ StartReason.NON_ACTIVITY -> reason // the start reason is already known: do nothing.
+ StartReason.ACTIVITY -> {
+ Metrics.startReasonActivityError.set(true)
+ logger.error("AppStartReasonProvider.Activity...onCreate unexpectedly called twice")
+ reason
+ }
+ }
+
+ activity.application.unregisterActivityLifecycleCallbacks(this) // we don't update the state further.
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/DefaultActivityLifecycleCallbacks.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/DefaultActivityLifecycleCallbacks.kt
new file mode 100644
index 0000000000..94dddd416c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/DefaultActivityLifecycleCallbacks.kt
@@ -0,0 +1,25 @@
+/* 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.focus.telemetry.startuptelemetry
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+
+/**
+ * An inheritance of [Application.ActivityLifecycleCallbacks] where each method has a default
+ * implementation that does nothing. This allows classes that extend this interface to have
+ * more concise definitions if they don't implement some methods; this is in the spirit of
+ * other `Default*` classes, such as [androidx.lifecycle.DefaultLifecycleObserver].
+ */
+interface DefaultActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
+ override fun onActivityStarted(activity: Activity) {}
+ override fun onActivityResumed(activity: Activity) {}
+ override fun onActivityPaused(activity: Activity) {}
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
+ override fun onActivityDestroyed(activity: Activity) {}
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupActivityLog.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupActivityLog.kt
new file mode 100644
index 0000000000..eb8546ef1e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupActivityLog.kt
@@ -0,0 +1,106 @@
+/* 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.focus.telemetry.startuptelemetry
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.NONE
+import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import mozilla.components.support.base.log.Log
+import mozilla.components.support.base.log.logger.Logger
+
+private val logger = Logger("StartupActivityLog")
+
+/**
+ * A record of the [Activity] created, started, and stopped events as well as [Application]
+ * foreground and background events. See [log] for the log. This class is expected to be
+ * registered in [Application.onCreate] by calling [registerInAppOnCreate].
+ *
+ * To prevent this list from growing infinitely, we clear the list when the application is stopped.
+ * This is acceptable from the current requirements: we never need to inspect more than the current
+ * start up.
+ */
+class StartupActivityLog {
+
+ private val _log = mutableListOf()
+ val log: List = _log
+
+ fun registerInAppOnCreate(
+ application: Application,
+ processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(),
+ ) {
+ processLifecycleOwner.lifecycle.addObserver(StartupLogAppLifecycleObserver())
+ application.registerActivityLifecycleCallbacks(StartupLogActivityLifecycleCallbacks())
+ }
+
+ @VisibleForTesting(otherwise = NONE)
+ fun getObserversForTesting() = Pair(StartupLogAppLifecycleObserver(), StartupLogActivityLifecycleCallbacks())
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun logEntries(loggerArg: Logger = logger, logLevel: Log.Priority = Log.logLevel) {
+ // Optimization: we want to avoid the potentially expensive conversions
+ // to Strings if we're not going to log anyway.
+ if (logLevel > Log.Priority.DEBUG) {
+ return
+ }
+
+ val transformedEntries = log.map {
+ when (it) {
+ is LogEntry.AppStarted -> "App-STARTED"
+ is LogEntry.AppStopped -> "App-STOPPED"
+ is LogEntry.ActivityCreated -> "${it.activityClass.simpleName}-CREATED"
+ is LogEntry.ActivityStarted -> "${it.activityClass.simpleName}-STARTED"
+ is LogEntry.ActivityStopped -> "${it.activityClass.simpleName}-STOPPED"
+ }
+ }
+
+ loggerArg.debug(transformedEntries.toString())
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupLogAppLifecycleObserver : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) {
+ _log.add(LogEntry.AppStarted)
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ logEntries()
+ _log.clear() // Optimization: see class kdoc for details.
+ _log.add(LogEntry.AppStopped)
+ }
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupLogActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ _log.add(LogEntry.ActivityCreated(activity::class.java))
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ _log.add(LogEntry.ActivityStarted(activity::class.java))
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ _log.add(LogEntry.ActivityStopped(activity::class.java))
+ }
+ }
+
+ /**
+ * A log entry with its detailed information for the [StartupActivityLog].
+ */
+ sealed class LogEntry {
+ object AppStarted : LogEntry()
+ object AppStopped : LogEntry()
+
+ data class ActivityCreated(val activityClass: Class) : LogEntry()
+ data class ActivityStarted(val activityClass: Class) : LogEntry()
+ data class ActivityStopped(val activityClass: Class) : LogEntry()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupPathProvider.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupPathProvider.kt
new file mode 100644
index 0000000000..92db3eab13
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupPathProvider.kt
@@ -0,0 +1,98 @@
+/* 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.focus.telemetry.startuptelemetry
+
+import android.app.Activity
+import android.content.Intent
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.NONE
+import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * This should be a member variable of [Activity] because its data is tied to the lifecycle of an
+ * Activity. Call [attachOnActivityOnCreate] & [onIntentReceived] for this class to work correctly.
+ */
+class StartupPathProvider {
+
+ enum class StartupPath {
+ MAIN,
+ VIEW,
+
+ /**
+ * The start up path if we received an Intent but we're unable to categorize it into other buckets.
+ */
+ UNKNOWN,
+
+ /**
+ * The start up path has not been set. This state includes:
+ * - this API is accessed before it is set
+ * - if no intent is received before the activity is STARTED (e.g. app switcher)
+ */
+ NOT_SET,
+ }
+
+ /**
+ * Returns the [StartupPath] for the currently started activity. This value will be set
+ * after an [Intent] is received that causes this activity to move into the STARTED state.
+ */
+ var startupPathForActivity = StartupPath.NOT_SET
+ private set
+
+ private var wasResumedSinceStartedState = false
+
+ fun attachOnActivityOnCreate(lifecycle: Lifecycle, intent: Intent?) {
+ lifecycle.addObserver(StartupPathLifecycleObserver())
+ onIntentReceived(intent)
+ }
+
+ // N.B.: this method duplicates the actual logic for determining what page to open.
+ // Unfortunately, it's difficult to re-use that logic because it occurs in many places throughout
+ // the code so we do the simple thing for now and duplicate it. It's noticeably different from
+ // what you might expect: e.g. ACTION_MAIN can open a URL and if ACTION_VIEW provides an invalid
+ // URL, it'll perform a MAIN action. However, it's fairly representative of what users *intended*
+ // to do when opening the app and shouldn't change much because it's based on Android system-wide
+ // conventions, so it's probably fine for our purposes.
+ private fun getStartupPathFromIntent(intent: Intent): StartupPath = when (intent.action) {
+ Intent.ACTION_MAIN -> StartupPath.MAIN
+ Intent.ACTION_VIEW -> StartupPath.VIEW
+ else -> StartupPath.UNKNOWN
+ }
+
+ /**
+ * Expected to be called when a new [Intent] is received by the [Activity]: i.e.
+ * [Activity.onCreate] and [Activity.onNewIntent].
+ */
+ fun onIntentReceived(intent: Intent?) {
+ // We want to set a path only if the intent causes the Activity to move into the STARTED state.
+ // This means we want to discard any intents that are received when the app is foregrounded.
+ // However, we can't use the Lifecycle.currentState to determine this because:
+ // - the app is briefly paused (state becomes STARTED) before receiving the Intent in
+ // the foreground so we can't say <= STARTED
+ // - onIntentReceived can be called from the CREATED or STARTED state so we can't say == CREATED
+ // So we're forced to track this state ourselves.
+ if (!wasResumedSinceStartedState && intent != null) {
+ startupPathForActivity = getStartupPathFromIntent(intent)
+ }
+ }
+
+ @VisibleForTesting(otherwise = NONE)
+ fun getTestCallbacks() = StartupPathLifecycleObserver()
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupPathLifecycleObserver : DefaultLifecycleObserver {
+ override fun onResume(owner: LifecycleOwner) {
+ wasResumedSinceStartedState = true
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ // Clear existing state.
+ startupPathForActivity = StartupPath.NOT_SET
+ wasResumedSinceStartedState = false
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupStateProvider.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupStateProvider.kt
new file mode 100644
index 0000000000..44664e5bdd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupStateProvider.kt
@@ -0,0 +1,137 @@
+/* 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.focus.telemetry.startuptelemetry
+
+import android.app.Activity
+import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider.StartReason
+import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog.LogEntry
+
+/**
+ * Identifies the "state" of start up where state can be COLD/WARM/HOT and possibly others.
+ *
+ * This class is nuanced: **please read the kdoc carefully before using it.** Consider contacting
+ * the perf team with your use case.
+ *
+ * For this class, we use the terminology from the [StartupActivityLog] such as STARTED and STOPPED.
+ * However, we're assuming STARTED means foregrounded and STOPPED means backgrounded. If this
+ * assumption is false, the logic in this class may be incorrect.
+ */
+class StartupStateProvider(
+ private val startupLog: StartupActivityLog,
+ private val startReasonProvider: AppStartReasonProvider,
+) {
+
+ enum class StartupState {
+ COLD, WARM, HOT,
+
+ /**
+ * A start up state where we weren't able to bucket it into the other categories.
+ * This includes, but is not limited to:
+ * - if the activity this is called from is not currently started
+ * - if the currently started activity is not the first started activity
+ */
+ UNKNOWN,
+ }
+
+ /**
+ * Returns the [StartupState] for the currently started activity. Note: the state will be
+ * [StartupState.UNKNOWN] if the currently started activity is not the first started activity.
+ *
+ * This method must be called after the foreground Activity is STARTED.
+ */
+ fun getStartupStateForStartedActivity(activityClass: Class): StartupState = when {
+ isColdStartForStartedActivity(activityClass) -> StartupState.COLD
+ isWarmStartForStartedActivity(activityClass) -> StartupState.WARM
+ isHotStartForStartedActivity(activityClass) -> StartupState.HOT
+ else -> StartupState.UNKNOWN
+ }
+
+ /**
+ * Returns true if the current startup state is COLD and the currently started activity is the
+ * first started activity (i.e. we can use it for performance measurements).
+ *
+ * This method must be called after the foreground Activity is STARTED.
+ */
+ fun isColdStartForStartedActivity(activityClass: Class): Boolean {
+ // A cold start means:
+ // - the process was started for the first started activity (e.g. not a service)
+ // - the first started activity ever is still active
+ //
+ // Thus, for the activity log we expect:
+ // [... Activity-STARTED, App-STARTED]
+ // since if another Activity was started, it would appear after App-STARTED. This is where:
+ // - the app has not been stopped ever
+ if (startReasonProvider.reason != StartReason.ACTIVITY) {
+ return false
+ }
+
+ val isLastStartedActivityStillStarted = startupLog.log.takeLast(2) == listOf(
+ LogEntry.ActivityStarted(activityClass),
+ LogEntry.AppStarted,
+ )
+ return !startupLog.log.contains(LogEntry.AppStopped) && isLastStartedActivityStillStarted
+ }
+
+ /**
+ * Returns true if the current startup state is WARM and the currently started activity is the
+ * first started activity for this start up (i.e. we can use it for performance measurements).
+ *
+ * This method must be called after the foreground activity is STARTED.
+ */
+ fun isWarmStartForStartedActivity(activityClass: Class): Boolean {
+ // A warm start means:
+ // - the app was backgrounded and has since been started
+ // - the first started activity since the app was started is still active.
+ // - that activity was created before being started
+ //
+ // For the activity log, we expect:
+ // [... App-STOPPED, ... Activity-CREATED, Activity-STARTED, App-STARTED]
+ // where:
+ // - App-STOPPED is the last STOPPED seen
+ // - we're assuming App-STARTED will only be last if one activity is started (as observed)
+ if (!startupLog.log.contains(LogEntry.AppStopped)) {
+ return false // if the app hasn't been stopped, it's not a warm start.
+ }
+ val afterLastStopped = startupLog.log.takeLastWhile { it != LogEntry.AppStopped }
+
+ @Suppress("MagicNumber")
+ return afterLastStopped.takeLast(3) == listOf(
+ LogEntry.ActivityCreated(activityClass),
+ LogEntry.ActivityStarted(activityClass),
+ LogEntry.AppStarted,
+ )
+ }
+
+ /**
+ * Returns true if the current startup state is HOT and the currently started activity is the
+ * first started activity for this start up (i.e. we can use it for performance measurements).
+ *
+ * This method must be called after the foreground activity is STARTED.
+ */
+ fun isHotStartForStartedActivity(activityClass: Class): Boolean {
+ // A hot start means:
+ // - the app was backgrounded and has since been started
+ // - the first started activity since the app was started is still active.
+ // - that activity was not created before being started
+ //
+ // For the activity log, we expect:
+ // [... App-STOPPED, ... Activity-STARTED, App-STARTED]
+ // where:
+ // - App-STOPPED is the last STOPPED seen
+ // - App-CREATED is NOT called for this activity
+ // - we're assuming App-STARTED will only be last if one activity is started (as observed)
+ if (!startupLog.log.contains(LogEntry.AppStopped)) {
+ return false // if the app hasn't been stopped, it's not a hot start.
+ }
+ val afterLastStopped = startupLog.log.takeLastWhile { it != LogEntry.AppStopped }
+
+ val isLastActivityStartedStillStarted = afterLastStopped.takeLast(2) == listOf(
+ LogEntry.ActivityStarted(activityClass),
+ LogEntry.AppStarted,
+ )
+ return !afterLastStopped.contains(LogEntry.ActivityCreated(activityClass)) &&
+ isLastActivityStartedStillStarted
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupTypeTelemetry.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupTypeTelemetry.kt
new file mode 100644
index 0000000000..1101d63a8c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/telemetry/startuptelemetry/StartupTypeTelemetry.kt
@@ -0,0 +1,110 @@
+/* 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.focus.telemetry.startuptelemetry
+
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.NONE
+import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.focus.GleanMetrics.PerfStartup
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider.StartupPath
+import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider.StartupState
+
+private val activityClass = MainActivity::class.java
+
+private val logger = Logger("StartupTypeTelemetry")
+
+/**
+ * Records telemetry for the number of start ups. See the
+ * [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
+ * for specific definitions.
+ *
+ * This should be a member variable of [MainActivity] because its data is tied to the lifecycle of an
+ * Activity. Call [attachOnMainActivityOnCreate] for this class to work correctly.
+ *
+ * N.B.: this class is lightly hardcoded to MainActivity.
+ */
+class StartupTypeTelemetry(
+ private val startupStateProvider: StartupStateProvider,
+ private val startupPathProvider: StartupPathProvider,
+) {
+
+ fun attachOnMainActivityOnCreate(lifecycle: Lifecycle) {
+ lifecycle.addObserver(StartupTypeLifecycleObserver())
+ }
+
+ private fun getTelemetryLabel(startupState: StartupState, startupPath: StartupPath): String {
+ // We don't use the enum name directly to avoid unintentional changes when refactoring.
+ val stateLabel = when (startupState) {
+ StartupState.COLD -> "cold"
+ StartupState.WARM -> "warm"
+ StartupState.HOT -> "hot"
+ StartupState.UNKNOWN -> "unknown"
+ }
+
+ val pathLabel = when (startupPath) {
+ StartupPath.MAIN -> "main"
+ StartupPath.VIEW -> "view"
+
+ // To avoid combinatorial explosion in label names, we bucket NOT_SET into UNKNOWN.
+ StartupPath.NOT_SET,
+ StartupPath.UNKNOWN,
+ -> "unknown"
+ }
+
+ return "${stateLabel}_$pathLabel"
+ }
+
+ @VisibleForTesting(otherwise = NONE)
+ fun getTestCallbacks() = StartupTypeLifecycleObserver()
+
+ /**
+ * Record startup telemetry based on the available [startupStateProvider] and [startupPathProvider].
+ *
+ * @param dispatcher used to control the thread on which telemetry will be recorded. Defaults to [Dispatchers.IO].
+ */
+ @VisibleForTesting(otherwise = PRIVATE)
+ fun record(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
+ val startupState = startupStateProvider.getStartupStateForStartedActivity(activityClass)
+ val startupPath = startupPathProvider.startupPathForActivity
+ val label = getTelemetryLabel(startupState, startupPath)
+
+ @OptIn(DelicateCoroutinesApi::class)
+ GlobalScope.launch(dispatcher) {
+ PerfStartup.startupType[label].add(1)
+ logger.info("Recorded start up: $label")
+ }
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ inner class StartupTypeLifecycleObserver : DefaultLifecycleObserver {
+ private var shouldRecordStart = false
+
+ override fun onStart(owner: LifecycleOwner) {
+ shouldRecordStart = true
+ }
+
+ override fun onResume(owner: LifecycleOwner) {
+ // We must record in onResume because the StartupStateProvider can only be called for
+ // STARTED activities and we can't guarantee our onStart is called before its.
+ //
+ // We only record if start was called for this resume to avoid recording
+ // for onPause -> onResume states.
+ if (shouldRecordStart) {
+ record()
+ shouldRecordStart = false
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/theme/Theme.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/theme/Theme.kt
new file mode 100644
index 0000000000..8ef1eab498
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/theme/Theme.kt
@@ -0,0 +1,14 @@
+/* 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.focus.theme
+
+import android.content.res.Resources
+import android.util.TypedValue
+
+fun Resources.Theme.resolveAttribute(attribute: Int): Int {
+ val typedValue = TypedValue()
+ resolveAttribute(attribute, typedValue, true)
+ return typedValue.resourceId
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesStorage.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesStorage.kt
new file mode 100644
index 0000000000..a04c4feb70
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesStorage.kt
@@ -0,0 +1,64 @@
+/* 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.focus.topsites
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import mozilla.components.feature.top.sites.PinnedSiteStorage
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.feature.top.sites.TopSitesFrecencyConfig
+import mozilla.components.feature.top.sites.TopSitesProviderConfig
+import mozilla.components.feature.top.sites.TopSitesStorage
+import mozilla.components.support.base.observer.Observable
+import mozilla.components.support.base.observer.ObserverRegistry
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Default implementation of [TopSitesStorage].
+ *
+ * @param pinnedSitesStorage An instance of [PinnedSiteStorage], used for storing pinned sites.
+ */
+class DefaultTopSitesStorage(
+ private val pinnedSitesStorage: PinnedSiteStorage,
+ coroutineContext: CoroutineContext = Dispatchers.IO,
+) : TopSitesStorage, Observable by ObserverRegistry() {
+
+ private var scope = CoroutineScope(coroutineContext)
+
+ override fun addTopSite(title: String, url: String, isDefault: Boolean) {
+ scope.launch {
+ pinnedSitesStorage.addPinnedSite(title, url, isDefault)
+
+ notifyObservers { onStorageUpdated() }
+ }
+ }
+
+ override fun removeTopSite(topSite: TopSite) {
+ scope.launch {
+ pinnedSitesStorage.removePinnedSite(topSite)
+
+ notifyObservers { onStorageUpdated() }
+ }
+ }
+
+ override fun updateTopSite(topSite: TopSite, title: String, url: String) {
+ scope.launch {
+ pinnedSitesStorage.updatePinnedSite(topSite, title, url)
+
+ notifyObservers { onStorageUpdated() }
+ }
+ }
+
+ override suspend fun getTopSites(
+ totalSites: Int,
+ frecencyConfig: TopSitesFrecencyConfig?,
+ providerConfig: TopSitesProviderConfig?,
+ ): List = pinnedSitesStorage.getPinnedSites().take(totalSites)
+
+ companion object {
+ const val TOP_SITES_MAX_LIMIT = 4
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesView.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesView.kt
new file mode 100644
index 0000000000..db0b7272db
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/DefaultTopSitesView.kt
@@ -0,0 +1,22 @@
+/* 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.focus.topsites
+
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.feature.top.sites.view.TopSitesView
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+
+/**
+ * The default implementation of [TopSitesView] for displaying the top site UI.
+ *
+ * @param store [AppStore] instance for dispatching the top sites changes.
+ */
+class DefaultTopSitesView(private val store: AppStore) : TopSitesView {
+
+ override fun displayTopSites(topSites: List) {
+ store.dispatch(AppAction.TopSitesChange(topSites))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/RenameTopSiteDialog.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/RenameTopSiteDialog.kt
new file mode 100644
index 0000000000..4eed0f8efa
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/RenameTopSiteDialog.kt
@@ -0,0 +1,47 @@
+/* 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.focus.topsites
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.res.stringResource
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.dialog.DialogInputField
+import org.mozilla.focus.ui.dialog.FocusDialog
+
+/**
+ * Display a dialog for renaming a top site
+ *
+ * @param currentName (event) Current top site name.
+ * @param onConfirm (event) Perform top site rename.
+ * @param onDismiss (event) Action to perform on dialog dismissal.
+ */
+@Composable
+fun RenameTopSiteDialog(
+ currentName: String,
+ onConfirm: (String) -> Unit,
+ onDismiss: () -> Unit,
+) {
+ var text by remember { mutableStateOf(currentName) }
+
+ FocusDialog(
+ dialogTitle = stringResource(R.string.rename_top_site),
+ dialogTextComposable = {
+ DialogInputField(
+ text = text,
+ placeholder = { Text(stringResource(id = R.string.placeholder_rename_top_site)) },
+ ) { newText -> text = newText }
+ },
+ onDismissRequest = { onDismiss.invoke() },
+ onConfirmRequest = { onConfirm.invoke(text) },
+ confirmButtonText = stringResource(android.R.string.ok),
+ dismissButtonText = stringResource(android.R.string.cancel),
+ isConfirmButtonEnabled = text.isNotEmpty(),
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSites.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSites.kt
new file mode 100644
index 0000000000..d754938b42
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSites.kt
@@ -0,0 +1,166 @@
+/* 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.focus.topsites
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.support.ktx.kotlin.getRepresentativeCharacter
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.menu.CustomDropdownMenu
+import org.mozilla.focus.ui.menu.MenuItem
+import org.mozilla.focus.ui.theme.focusColors
+
+/**
+ * A list of top sites.
+ *
+ * @param topSites List of [TopSite] to display.
+ * @param onTopSiteClicked Invoked when the user clicked the top site
+ * @param onRemoveTopSiteClicked Invoked when the user clicked 'Remove' item from drop down menu
+ * @param onRenameTopSiteClicked Invoked when the user clicked 'Rename' item from drop down menu
+ */
+
+@Composable
+fun TopSites(
+ topSites: List,
+ onTopSiteClicked: (TopSite) -> Unit,
+ onRemoveTopSiteClicked: (TopSite) -> Unit,
+ onRenameTopSiteClicked: (TopSite) -> Unit,
+) {
+ Row(
+ modifier = Modifier
+ .padding(horizontal = 10.dp)
+ .size(width = 324.dp, height = 84.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(28.dp),
+ ) {
+ topSites.forEach { topSite ->
+ TopSiteItem(
+ topSite = topSite,
+ menuItems = listOfNotNull(
+ MenuItem(
+ title = stringResource(R.string.rename_top_site_item),
+ onClick = { onRenameTopSiteClicked(topSite) },
+ ),
+ MenuItem(
+ title = stringResource(R.string.remove_top_site),
+ onClick = { onRemoveTopSiteClicked(topSite) },
+ ),
+ ),
+ onTopSiteClick = { item -> onTopSiteClicked(item) },
+ )
+ }
+ }
+}
+
+/**
+ * A top site item.
+ *
+ * @param topSite The [TopSite] to display.
+ * @param menuItems List of [MenuItem] to display in a top site dropdown menu.
+ * @param onTopSiteClick Invoked when the user clicks on a top site.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun TopSiteItem(
+ topSite: TopSite,
+ menuItems: List,
+ onTopSiteClick: (TopSite) -> Unit = {},
+) {
+ var menuExpanded by remember { mutableStateOf(false) }
+
+ Box {
+ Column(
+ modifier = Modifier
+ .combinedClickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = { onTopSiteClick(topSite) },
+ onLongClick = { menuExpanded = true },
+ )
+ .size(width = 60.dp, height = 84.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ TopSiteFaviconCard(topSite = topSite)
+
+ Text(
+ text = topSite.title ?: topSite.url,
+ modifier = Modifier.padding(top = 8.dp),
+ color = focusColors.topSiteTitle,
+ fontSize = 12.sp,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+
+ CustomDropdownMenu(
+ menuItems = menuItems,
+ isExpanded = menuExpanded,
+ onDismissClicked = { menuExpanded = false },
+ )
+ }
+ }
+}
+
+/**
+ * The top site favicon card.
+ *
+ * @param topSite The [TopSite] to display.
+ */
+@Composable
+private fun TopSiteFaviconCard(topSite: TopSite) {
+ Card(
+ modifier = Modifier.size(60.dp),
+ shape = RoundedCornerShape(8.dp),
+ backgroundColor = focusColors.topSiteBackground,
+ ) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Surface(
+ modifier = Modifier.size(36.dp),
+ shape = RoundedCornerShape(4.dp),
+ color = focusColors.surface,
+ ) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = if (topSite.title.isNullOrEmpty()) {
+ topSite.url.getRepresentativeCharacter()
+ } else {
+ topSite.title?.get(0).toString()
+ },
+ color = focusColors.topSiteFaviconText,
+ fontSize = 20.sp,
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSitesOverlay.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSitesOverlay.kt
new file mode 100644
index 0000000000..916e8f2edc
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/topsites/TopSitesOverlay.kt
@@ -0,0 +1,112 @@
+/* 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.focus.topsites
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.lib.state.ext.observeAsComposableState
+import org.mozilla.focus.Components
+import org.mozilla.focus.GleanMetrics.Shortcuts
+import org.mozilla.focus.components
+import org.mozilla.focus.state.AppAction
+
+@OptIn(DelicateCoroutinesApi::class)
+@Composable
+fun TopSitesOverlay(modifier: Modifier = Modifier) {
+ val components = components
+ val topSitesState = components.appStore.observeAsComposableState { state -> state.topSites }
+ val topSites = topSitesState.value ?: listOf()
+ val showRenameDialog: MutableState = remember { mutableStateOf(false) }
+ val topSiteItem: MutableState = remember { mutableStateOf(null) }
+
+ if (!topSites.isNullOrEmpty()) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(24.dp))
+
+ TopSites(
+ topSites = topSites,
+ onTopSiteClicked = { item -> openTopSite(item, components) },
+ onRemoveTopSiteClicked = { item ->
+ removeTopSite(item, components)
+ },
+ onRenameTopSiteClicked = { topSite ->
+ showRenameDialog.value = true
+ topSiteItem.value = topSite
+ },
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+ }
+
+ if (showRenameDialog.value) {
+ topSiteItem.value?.let { selectedTopSite ->
+ RenameTopSiteDialog(
+ currentName = selectedTopSite.title ?: "",
+ onConfirm = { newTitle ->
+ renameTopSite(selectedTopSite, newTitle, components)
+ showRenameDialog.value = false
+ topSiteItem.value = null
+ },
+ onDismiss = {
+ showRenameDialog.value = false
+ topSiteItem.value = null
+ },
+ )
+ }
+ }
+ }
+}
+
+private fun openTopSite(item: TopSite, components: Components) {
+ val currentTabId = components.store.state.selectedTabId
+ if (currentTabId.isNullOrEmpty()) {
+ components.tabsUseCases.addTab(
+ url = item.url,
+ source = SessionState.Source.Internal.HomeScreen,
+ selectTab = true,
+ private = true,
+ )
+ } else {
+ components.sessionUseCases.loadUrl(item.url, currentTabId)
+ components.appStore.dispatch(AppAction.FinishEdit(currentTabId))
+ }
+
+ Shortcuts.shortcutOpenedCounter.add()
+}
+
+@OptIn(DelicateCoroutinesApi::class)
+fun removeTopSite(item: TopSite, components: Components) {
+ Shortcuts.shortcutRemovedCounter["removed_from_home_screen"].add()
+
+ GlobalScope.launch(Dispatchers.IO) {
+ components.topSitesUseCases.removeTopSites(item)
+ }
+}
+
+@OptIn(DelicateCoroutinesApi::class)
+fun renameTopSite(selectedTopSite: TopSite, newTitle: String, components: Components) {
+ GlobalScope.launch(Dispatchers.IO) {
+ components.topSitesUseCases.updateTopSites.invoke(
+ selectedTopSite,
+ newTitle,
+ selectedTopSite.url,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/dialog/FocusDialog.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/dialog/FocusDialog.kt
new file mode 100644
index 0000000000..590077356f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/dialog/FocusDialog.kt
@@ -0,0 +1,209 @@
+/* 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.focus.ui.dialog
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.material.TextField
+import androidx.compose.material.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusColors
+import org.mozilla.focus.ui.theme.focusTypography
+
+/**
+ * Reusable composable for an alert dialog.
+ *
+ * @param dialogTitle (mandatory) text displayed as the dialog title.
+ * @param dialogTextComposable (optional) A composable displayed as dialog text
+ * @param dialogText (optional) A text displayed as dialog text when [dialogTextComposable] is null.
+ * @param onConfirmRequest (event) Action to perform on dialog confirm action.
+ * @param onDismissRequest (event) Action to perform on dialog dismissal.
+ * @param isConfirmButtonEnabled (optional) whether the confirm [DialogTextButton] is enabled. Default value is true.
+ * @param isDismissButtonEnabled (optional) whether the confirm [DialogTextButton] is enabled. Default value is true.
+ * @param isConfirmButtonVisible (optional) whether the confirm [DialogTextButton] is visible. Default value is true.
+ * @param isDismissButtonVisible (optional)whether the confirm [DialogTextButton] is visible. Default value is true.
+ *
+ */
+@Suppress("LongParameterList")
+@Composable
+fun FocusDialog(
+ dialogTitle: String,
+ dialogTextComposable: @Composable (() -> Unit)? = null,
+ dialogText: String = "",
+ onConfirmRequest: () -> Unit,
+ onDismissRequest: () -> Unit,
+ confirmButtonText: String = "null",
+ dismissButtonText: String = "null",
+ isConfirmButtonEnabled: Boolean = true,
+ isDismissButtonEnabled: Boolean = true,
+ isConfirmButtonVisible: Boolean = true,
+ isDismissButtonVisible: Boolean = true,
+) {
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ title = { DialogTitle(text = dialogTitle) },
+ text = dialogTextComposable ?: { DialogText(text = dialogText) },
+ confirmButton = {
+ if (isConfirmButtonVisible) {
+ DialogTextButton(
+ text = confirmButtonText,
+ onClick = onConfirmRequest,
+ enabled = isConfirmButtonEnabled,
+ )
+ }
+ },
+ dismissButton = {
+ if (isDismissButtonVisible) {
+ DialogTextButton(
+ text = dismissButtonText,
+ onClick = onDismissRequest,
+ enabled = isDismissButtonEnabled,
+ )
+ }
+ },
+ backgroundColor = focusColors.secondary,
+ shape = MaterialTheme.shapes.medium,
+ )
+}
+
+/**
+ * Reusable composable for a dialog title.
+ */
+@Composable
+fun DialogTitle(
+ modifier: Modifier = Modifier,
+ text: String,
+) {
+ Text(
+ modifier = modifier,
+ color = focusColors.onPrimary,
+ text = text,
+ style = focusTypography.dialogTitle,
+ )
+}
+
+/**
+ * Reusable composable for a dialog text.
+ */
+@Composable
+fun DialogText(
+ modifier: Modifier = Modifier,
+ text: String,
+) {
+ Text(
+ modifier = modifier,
+ color = focusColors.onPrimary,
+ text = text,
+ style = focusTypography.dialogInput,
+ )
+}
+
+/**
+ * Reusable composable for a dialog button with text.
+ */
+@Composable
+fun DialogTextButton(
+ text: String,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ onClick: () -> Unit,
+) {
+ TextButton(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ shape = MaterialTheme.shapes.large,
+ ) {
+ Text(
+ modifier = modifier,
+ color = if (enabled) {
+ focusColors.dialogActiveControls
+ } else {
+ focusColors.dialogActiveControls.copy(alpha = 0.5f)
+ },
+ text = text,
+ style = MaterialTheme.typography.button,
+ )
+ }
+}
+
+/**
+ * Reusable composable for a dialog input field.
+ */
+@Composable
+fun DialogInputField(
+ modifier: Modifier = Modifier,
+ text: String,
+ placeholder: @Composable () -> Unit,
+ onValueChange: (String) -> Unit,
+) {
+ TextField(
+ modifier = modifier
+ .wrapContentHeight(),
+ value = text,
+ placeholder = placeholder,
+ onValueChange = onValueChange,
+ textStyle = focusTypography.dialogInput,
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = focusColors.secondary,
+ textColor = focusColors.onSecondary,
+ cursorColor = focusColors.onPrimary,
+ focusedIndicatorColor = focusColors.dialogActiveControls,
+ unfocusedIndicatorColor = focusColors.dialogActiveControls,
+ ),
+ singleLine = true,
+ shape = MaterialTheme.shapes.large,
+ )
+}
+
+@Preview(
+ name = "dark theme",
+ showBackground = true,
+ backgroundColor = 0xFF393473,
+ uiMode = Configuration.UI_MODE_NIGHT_MASK,
+)
+@Composable
+fun DialogTitlePreviewDark() {
+ FocusTheme {
+ FocusDialogSample()
+ }
+}
+
+@Preview(
+ name = "light theme",
+ showBackground = true,
+ backgroundColor = 0xFFFBFBFE,
+ uiMode = Configuration.UI_MODE_NIGHT_NO,
+)
+@Composable
+fun DialogTitlePreviewLight() {
+ FocusTheme {
+ FocusDialogSample()
+ }
+}
+
+@Composable
+fun FocusDialogSample() {
+ FocusDialog(
+ dialogTitle = "Sample dialog",
+ dialogTextComposable = null,
+ dialogText = "Sample dialog text",
+ onConfirmRequest = { },
+ onDismissRequest = { },
+ confirmButtonText = "OK",
+ dismissButtonText = "CANCEL",
+ isConfirmButtonEnabled = true,
+ isDismissButtonEnabled = true,
+ isConfirmButtonVisible = true,
+ isDismissButtonVisible = true,
+ )
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/CustomDropdownMenu.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/CustomDropdownMenu.kt
new file mode 100644
index 0000000000..f9ae7a5b41
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/CustomDropdownMenu.kt
@@ -0,0 +1,46 @@
+/* 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.focus.ui.menu
+
+import androidx.compose.foundation.background
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import org.mozilla.focus.ui.theme.focusColors
+
+/**
+ * Menu used to offer a list of options in a drop-down manner when its parent is clicked
+ *
+ * @param menuItems The list of options
+ * @param isExpanded True if the menu is open
+ * @param onDismissClicked Invoked when the user dismiss the menu
+ */
+@Composable
+fun CustomDropdownMenu(
+ menuItems: List,
+ isExpanded: Boolean,
+ onDismissClicked: () -> Unit,
+) {
+ DropdownMenu(
+ expanded = isExpanded,
+ onDismissRequest = onDismissClicked,
+ modifier = Modifier.background(color = focusColors.menuBackground),
+ ) {
+ for (item in menuItems) {
+ DropdownMenuItem(
+ onClick = {
+ item.onClick()
+ onDismissClicked.invoke()
+ },
+ ) {
+ Text(
+ text = item.title,
+ color = focusColors.menuText,
+ )
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/MenuItem.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/MenuItem.kt
new file mode 100644
index 0000000000..6659c1b237
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/menu/MenuItem.kt
@@ -0,0 +1,16 @@
+/* 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.focus.ui.menu
+
+/**
+ * A menu item in dropdown menu.
+ *
+ * @property title The menu item title.
+ * @property onClick Invoked when the user clicks on the menu item.
+ */
+data class MenuItem(
+ val title: String,
+ val onClick: () -> Unit,
+)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusColors.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusColors.kt
new file mode 100644
index 0000000000..1ee2406016
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusColors.kt
@@ -0,0 +1,49 @@
+/* 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.focus.ui.theme
+
+import androidx.compose.material.Colors
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Custom Focus colors, other than baseline Material color theme.
+ * Colors here should be added with consideration, only when an existing Material baseline color is not suitable.
+ */
+data class FocusColors(
+ val material: Colors,
+ val dialogActiveControls: Color,
+ val topSiteBackground: Color,
+ val topSiteFaviconText: Color,
+ val topSiteTitle: Color,
+ val menuBackground: Color,
+ val menuText: Color,
+ val aboutPageText: Color,
+ val aboutPageLink: Color,
+ val radioButtonSelected: Color,
+ val toolbarColor: Color,
+ val privacySecuritySettingsToolTip: Color,
+ val onboardingButtonBackground: Color,
+ val onboardingKeyFeatureImageTint: Color,
+ val onboardingSemiBoldText: Color,
+ val onboardingNormalText: Color,
+ val settingsTextColor: Color,
+ val settingsTextSummaryColor: Color,
+ val closeIcon: Color,
+ val dialogTextColor: Color,
+) {
+ val primary: Color get() = material.primary
+ val primaryVariant: Color get() = material.primaryVariant
+ val secondary: Color get() = material.secondary
+ val secondaryVariant: Color get() = material.secondaryVariant
+ val background: Color get() = material.background
+ val surface: Color get() = material.surface
+ val error: Color get() = material.error
+ val onPrimary: Color get() = material.onPrimary
+ val onSecondary: Color get() = material.onSecondary
+ val onBackground: Color get() = material.onBackground
+ val onSurface: Color get() = material.onSurface
+ val onError: Color get() = material.onError
+ val isLight: Boolean get() = material.isLight
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusDimensions.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusDimensions.kt
new file mode 100644
index 0000000000..8c79ef17cc
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusDimensions.kt
@@ -0,0 +1,15 @@
+/* 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.focus.ui.theme
+
+import androidx.compose.ui.unit.TextUnit
+
+/**
+ * Custom Focus dimensions
+ */
+data class FocusDimensions(
+ val onboardingTitle: TextUnit,
+ val onboardingSubtitleOne: TextUnit,
+ val onboardingSubtitleTwo: TextUnit,
+)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTheme.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTheme.kt
new file mode 100644
index 0000000000..bbc7609c16
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTheme.kt
@@ -0,0 +1,138 @@
+/* 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.focus.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.Colors
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.sp
+import mozilla.components.ui.colors.PhotonColors
+
+private const val TABLET_SIZE = 600
+
+val localColors = staticCompositionLocalOf { lightColorPalette() }
+val localDimensions = staticCompositionLocalOf { phoneDimensions() }
+
+/**
+ * The theme used for Firefox Focus/Klar for Android.
+ */
+@Composable
+fun FocusTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit,
+) {
+ val dimensions = if (LocalConfiguration.current.screenWidthDp <= TABLET_SIZE) {
+ phoneDimensions()
+ } else {
+ tabletDimensions()
+ }
+
+ val colors = if (darkTheme) {
+ darkColorPalette()
+ } else {
+ lightColorPalette()
+ }
+
+ CompositionLocalProvider(localColors provides colors, localDimensions provides dimensions) {
+ MaterialTheme(
+ colors = colors.material,
+ typography = focusTypography.materialTypography,
+ content = content,
+ )
+ }
+}
+
+val focusColors: FocusColors
+ @Composable
+ @ReadOnlyComposable
+ get() = localColors.current
+
+val focusDimensions: FocusDimensions
+ @Composable
+ @ReadOnlyComposable
+ get() = localDimensions.current
+
+private fun darkColorPalette(): FocusColors = FocusColors(
+ material = darkColorsMaterial(),
+ dialogActiveControls = PhotonColors.Pink40,
+ topSiteBackground = PhotonColors.Ink05,
+ topSiteFaviconText = PhotonColors.LightGrey05,
+ topSiteTitle = PhotonColors.LightGrey05,
+ menuBackground = PhotonColors.Ink05,
+ menuText = PhotonColors.White,
+ aboutPageText = PhotonColors.White,
+ aboutPageLink = PhotonColors.Pink70,
+ radioButtonSelected = PhotonColors.Pink70,
+ toolbarColor = PhotonColors.White,
+ privacySecuritySettingsToolTip = PhotonColors.White,
+ onboardingKeyFeatureImageTint = PhotonColors.LightGrey05,
+ onboardingButtonBackground = PhotonColors.Violet50,
+ onboardingSemiBoldText = PhotonColors.LightGrey05,
+ onboardingNormalText = PhotonColors.LightGrey50,
+ settingsTextColor = PhotonColors.White,
+ settingsTextSummaryColor = PhotonColors.LightGrey50,
+ closeIcon = PhotonColors.LightGrey70,
+ dialogTextColor = PhotonColors.White,
+)
+
+private fun lightColorPalette(): FocusColors = FocusColors(
+ material = lightColorsMaterial(),
+ dialogActiveControls = PhotonColors.Pink70,
+ topSiteBackground = PhotonColors.White,
+ topSiteFaviconText = PhotonColors.Ink50,
+ topSiteTitle = PhotonColors.DarkGrey05,
+ menuBackground = PhotonColors.White,
+ menuText = PhotonColors.Black,
+ aboutPageText = PhotonColors.Black,
+ aboutPageLink = PhotonColors.Pink70,
+ radioButtonSelected = PhotonColors.Pink70,
+ toolbarColor = PhotonColors.Black,
+ privacySecuritySettingsToolTip = PhotonColors.White,
+ settingsTextColor = PhotonColors.Black,
+ settingsTextSummaryColor = PhotonColors.DarkGrey05,
+ onboardingKeyFeatureImageTint = PhotonColors.DarkGrey90,
+ onboardingButtonBackground = PhotonColors.Ink20,
+ onboardingSemiBoldText = PhotonColors.DarkGrey90,
+ onboardingNormalText = PhotonColors.DarkGrey05,
+ closeIcon = PhotonColors.LightGrey90,
+ dialogTextColor = PhotonColors.Black,
+)
+
+/**
+ * Material baseline colors can be overridden here.
+ */
+private fun darkColorsMaterial(): Colors = darkColors(
+ secondary = PhotonColors.Ink05,
+ surface = PhotonColors.Ink60,
+ onSurface = PhotonColors.LightGrey05,
+ onBackground = PhotonColors.LightGrey05,
+ onPrimary = PhotonColors.LightGrey05,
+)
+
+private fun lightColorsMaterial(): Colors = lightColors(
+ secondary = PhotonColors.LightGrey05,
+ surface = PhotonColors.Violet05,
+ onSurface = PhotonColors.Ink50,
+ onBackground = PhotonColors.Ink50,
+ onPrimary = PhotonColors.Ink50,
+)
+fun phoneDimensions() = FocusDimensions(
+ onboardingTitle = 24.sp,
+ onboardingSubtitleOne = 16.sp,
+ onboardingSubtitleTwo = 14.sp,
+)
+
+fun tabletDimensions() = FocusDimensions(
+ onboardingTitle = 28.sp,
+ onboardingSubtitleOne = 18.sp,
+ onboardingSubtitleTwo = 18.sp,
+)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTypography.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTypography.kt
new file mode 100644
index 0000000000..f8b34ce5ad
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTypography.kt
@@ -0,0 +1,136 @@
+/* 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.focus.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.sp
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.focus.R
+
+val metropolis = FontFamily(
+ Font(R.font.metropolis, FontWeight.Normal),
+ Font(R.font.metropolis_bold, FontWeight.Bold),
+ Font(R.font.metropolis_semibold, FontWeight.SemiBold),
+)
+
+/**
+ * Custom Focus typography, other than baseline Material typography.
+ * Custom text styles should be added with consideration,
+ * only when an existing Material style is not suitable.
+ */
+data class FocusTypography(
+ val materialTypography: Typography,
+ val links: TextStyle,
+ val dialogTitle: TextStyle,
+ val dialogInput: TextStyle,
+ val dialogContent: TextStyle,
+ val onboardingTitle: TextStyle,
+ val onboardingSubtitle: TextStyle,
+ val onboardingDescription: TextStyle,
+ val onboardingFeatureTitle: TextStyle,
+ val onboardingFeatureDescription: TextStyle,
+ val onboardingButton: TextStyle,
+ val cfrTextStyle: TextStyle,
+ val cfrCookieBannerTextStyle: TextStyle,
+) {
+ val h1: TextStyle get() = materialTypography.h1
+ val h2: TextStyle get() = materialTypography.h2
+ val h3: TextStyle get() = materialTypography.h3
+ val h4: TextStyle get() = materialTypography.h4
+ val h5: TextStyle get() = materialTypography.h5
+ val h6: TextStyle get() = materialTypography.h6
+ val subtitle1: TextStyle get() = materialTypography.subtitle1
+ val subtitle2: TextStyle get() = materialTypography.subtitle2
+ val body1: TextStyle get() = materialTypography.body1
+ val body2: TextStyle get() = materialTypography.body2
+ val button: TextStyle get() = materialTypography.button
+ val caption: TextStyle get() = materialTypography.caption
+ val overline: TextStyle get() = materialTypography.overline
+}
+
+val focusTypography: FocusTypography
+ @Composable
+ @ReadOnlyComposable
+ get() = FocusTypography(
+ materialTypography = Typography(
+ body1 = TextStyle(
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ ),
+ ),
+ links = TextStyle(
+ fontSize = 16.sp,
+ textDecoration = TextDecoration.Underline,
+ lineHeight = 24.sp,
+ ),
+ dialogTitle = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 20.sp,
+ ),
+ dialogInput = TextStyle(
+ fontFamily = metropolis,
+ fontSize = 20.sp,
+ color = focusColors.onPrimary,
+ ),
+ dialogContent = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ ),
+ onboardingTitle = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = focusDimensions.onboardingTitle,
+ color = focusColors.onboardingSemiBoldText,
+ ),
+ onboardingSubtitle = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.Normal,
+ fontSize = focusDimensions.onboardingSubtitleOne,
+ color = focusColors.onboardingSemiBoldText,
+ ),
+ onboardingDescription = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.Normal,
+ fontSize = focusDimensions.onboardingSubtitleTwo,
+ color = focusColors.onboardingNormalText,
+ ),
+ onboardingFeatureTitle = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 14.sp,
+ color = focusColors.onboardingSemiBoldText,
+ ),
+ onboardingFeatureDescription = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ color = focusColors.onboardingNormalText,
+ ),
+ onboardingButton = TextStyle(
+ fontFamily = metropolis,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 14.sp,
+ color = PhotonColors.LightGrey05,
+ ),
+ cfrTextStyle = TextStyle(
+ fontSize = 16.sp,
+ letterSpacing = 0.5.sp,
+ lineHeight = 24.sp,
+ ),
+ cfrCookieBannerTextStyle = TextStyle(
+ fontSize = 14.sp,
+ letterSpacing = 0.25.sp,
+ lineHeight = 20.sp,
+ ),
+ )
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/AppConstants.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/AppConstants.kt
new file mode 100644
index 0000000000..312253a2eb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/AppConstants.kt
@@ -0,0 +1,33 @@
+/* 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.focus.utils
+
+import org.mozilla.focus.BuildConfig
+
+object AppConstants {
+ private const val BUILD_TYPE_RELEASE = "release"
+ private const val BUILD_TYPE_BETA = "beta"
+ private const val BUILD_TYPE_DEBUG = "debug"
+ private const val BUILD_TYPE_NIGHTLY = "nightly"
+ private const val PRODUCT_FLAVOR_KLAR = "klar"
+
+ val isKlarBuild: Boolean
+ get() = PRODUCT_FLAVOR_KLAR == BuildConfig.FLAVOR
+
+ val isReleaseBuild: Boolean
+ get() = BUILD_TYPE_RELEASE == BuildConfig.BUILD_TYPE
+
+ val isBetaBuild: Boolean
+ get() = BUILD_TYPE_BETA == BuildConfig.BUILD_TYPE
+
+ val isNightlyBuild: Boolean
+ get() = BUILD_TYPE_NIGHTLY == BuildConfig.BUILD_TYPE
+
+ val isDevBuild: Boolean
+ get() = BUILD_TYPE_DEBUG == BuildConfig.BUILD_TYPE
+
+ val isDevOrNightlyBuild: Boolean
+ get() = isDevBuild || isNightlyBuild
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ClickableSubstringLink.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ClickableSubstringLink.kt
new file mode 100644
index 0000000000..c811e086c2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ClickableSubstringLink.kt
@@ -0,0 +1,112 @@
+/* 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.focus.utils
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.sp
+import org.mozilla.focus.R
+import org.mozilla.focus.ui.theme.FocusTheme
+import org.mozilla.focus.ui.theme.focusTypography
+
+/**
+ * [Text] containing a substring styled as an URL informing when this is clicked.
+ *
+ * @param text Full text that will be displayed
+ * @param textColor [Color] of the normal text. The URL substring will have a default URL style applied.
+ * @param linkTextColor [Color] of the link text.
+ * @param style of the text
+ * @param linkTextDecoration The decorations to paint on the link text (e.g., an underline).
+ * @param clickableStartIndex [text] index at which the URL substring starts.
+ * @param clickableEndIndex [text] index at which the URL substring ends.
+ * @param onClick Callback to be invoked only when the URL substring is clicked.
+ */
+@Composable
+fun ClickableSubstringLink(
+ text: String,
+ textColor: Color = colorResource(R.color.cfr_text_color),
+ linkTextColor: Color = colorResource(R.color.cfr_text_color),
+ style: TextStyle = focusTypography.body1,
+ linkTextDecoration: TextDecoration? = null,
+ clickableStartIndex: Int,
+ clickableEndIndex: Int,
+ onClick: () -> Unit,
+) {
+ val annotatedText = buildAnnotatedString {
+ append(text)
+
+ addStyle(
+ SpanStyle(textColor),
+ start = 0,
+ end = clickableStartIndex,
+ )
+
+ addStyle(
+ SpanStyle(
+ color = linkTextColor,
+ textDecoration = linkTextDecoration,
+ ),
+ start = clickableStartIndex,
+ end = clickableEndIndex,
+ )
+
+ addStyle(
+ SpanStyle(textColor),
+ start = clickableEndIndex,
+ end = text.length,
+ )
+
+ addStyle(
+ SpanStyle(fontSize = 12.sp),
+ start = 0,
+ end = clickableEndIndex,
+ )
+
+ addStringAnnotation(
+ tag = "link",
+ annotation = "",
+ start = clickableStartIndex,
+ end = clickableEndIndex,
+ )
+ }
+
+ ClickableText(
+ text = annotatedText,
+ style = style,
+ onClick = {
+ annotatedText
+ .getStringAnnotations("link", it, it)
+ .firstOrNull()?.let {
+ onClick()
+ }
+ },
+ )
+}
+
+@Composable
+@Preview
+private fun ClickableSubstringTextPreview() {
+ val text = "This text contains a link"
+
+ FocusTheme {
+ Box {
+ ClickableSubstringLink(
+ text = text,
+ linkTextDecoration = TextDecoration.Underline,
+ clickableStartIndex = text.indexOf("link"),
+ clickableEndIndex = text.length,
+ ) { }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Features.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Features.kt
new file mode 100644
index 0000000000..45c601f060
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Features.kt
@@ -0,0 +1,15 @@
+/* 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.focus.utils
+
+/**
+ * Simple feature flags.
+ */
+object Features {
+ /**
+ * Delete all shortcuts when New Session Button from FingerPrint LockScreen is clicked
+ */
+ var DELETE_TOP_SITES_WHEN_NEW_SESSION_BUTTON_CLICKED: Boolean = AppConstants.isDevOrNightlyBuild
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbar.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbar.kt
new file mode 100644
index 0000000000..44fee6d98b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbar.kt
@@ -0,0 +1,146 @@
+/* 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.focus.utils
+
+import android.content.Context
+import android.graphics.Color
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.isVisible
+import androidx.core.view.updatePadding
+import com.google.android.material.snackbar.BaseTransientBottomBar
+import com.google.android.material.snackbar.ContentViewCallback
+import com.google.android.material.snackbar.Snackbar
+import org.mozilla.focus.databinding.FocusSnackbarBinding
+import org.mozilla.focus.ext.settings
+
+class FocusSnackbar private constructor(
+ parent: ViewGroup,
+ private val binding: FocusSnackbarBinding,
+ contentViewCallback: FocusSnackbarCallback,
+) : BaseTransientBottomBar(parent, binding.root, contentViewCallback) {
+
+ init {
+ view.setBackgroundColor(Color.TRANSPARENT)
+ view.setPadding(0, 0, 0, 0)
+ }
+
+ fun setText(text: String) = apply {
+ binding.snackbarText.text = text
+ }
+
+ /**
+ * Sets an action to be performed on clicking [FocusSnackbar]'s action button.
+ */
+ fun setAction(text: String, action: (Context) -> Unit) = apply {
+ binding.snackbarAction.apply {
+ setText(text)
+ isVisible = true
+ setOnClickListener {
+ action.invoke(it.context)
+ dismiss()
+ }
+ }
+ }
+
+ companion object {
+ const val LENGTH_LONG = Snackbar.LENGTH_LONG
+ const val LENGTH_SHORT = Snackbar.LENGTH_SHORT
+ private const val LENGTH_ACCESSIBLE = 15000 // 15 seconds in ms
+
+ /**
+ * Display a custom Focus Snackbar in the given view with duration and proper styling.
+ */
+ fun make(
+ view: View,
+ duration: Int = LENGTH_LONG,
+ ): FocusSnackbar {
+ val parent = findSuitableParent(view) ?: run {
+ throw IllegalArgumentException(
+ "No suitable parent found from the given view. Please provide a valid view.",
+ )
+ }
+
+ val inflater = LayoutInflater.from(parent.context)
+ val binding = FocusSnackbarBinding.inflate(inflater, parent, false)
+
+ val durationOrAccessibleDuration =
+ if (parent.context.settings.isAccessibilityEnabled()) {
+ LENGTH_ACCESSIBLE
+ } else {
+ duration
+ }
+
+ val callback = FocusSnackbarCallback(binding.root)
+
+ return FocusSnackbar(parent, binding, callback).also {
+ it.duration = durationOrAccessibleDuration
+ it.view.updatePadding(
+ bottom = 0,
+ )
+ }
+ }
+
+ // Use the same implementation of Android `Snackbar`
+ @SuppressWarnings("FunctionParameterNaming")
+ private fun findSuitableParent(_view: View?): ViewGroup? {
+ var view = _view
+ var fallback: ViewGroup? = null
+
+ do {
+ if (view is CoordinatorLayout) {
+ return view
+ }
+
+ if (view is FrameLayout) {
+ if (view.id == android.R.id.content) {
+ return view
+ }
+
+ fallback = view
+ }
+
+ if (view != null) {
+ val parent = view.parent
+ view = if (parent is View) parent else null
+ }
+ } while (view != null)
+
+ return fallback
+ }
+ }
+}
+
+private class FocusSnackbarCallback(
+ private val content: View,
+) : ContentViewCallback {
+
+ override fun animateContentIn(delay: Int, duration: Int) {
+ content.translationY = (content.height).toFloat()
+ content.animate().apply {
+ translationY(defaultYTranslation)
+ setDuration(animateInDuration)
+ startDelay = delay.toLong()
+ }
+ }
+
+ override fun animateContentOut(delay: Int, duration: Int) {
+ content.translationY = defaultYTranslation
+ content.animate().apply {
+ translationY((content.height).toFloat())
+ setDuration(animateOutDuration)
+ startDelay = delay.toLong()
+ }
+ }
+
+ companion object {
+ private const val defaultYTranslation = 0f
+ private const val animateInDuration = 200L
+ private const val animateOutDuration = 150L
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbarDelegate.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbarDelegate.kt
new file mode 100644
index 0000000000..2c0cc13a49
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/FocusSnackbarDelegate.kt
@@ -0,0 +1,37 @@
+/* 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.focus.utils
+
+import android.view.View
+import androidx.annotation.StringRes
+import mozilla.components.ui.widgets.SnackbarDelegate
+
+class FocusSnackbarDelegate(private val view: View) : SnackbarDelegate {
+
+ override fun show(
+ snackBarParentView: View,
+ @StringRes text: Int,
+ duration: Int,
+ @StringRes action: Int,
+ listener: ((v: View) -> Unit)?,
+ ) {
+ if (listener != null && action != 0) {
+ FocusSnackbar.make(
+ view = view,
+ duration = FocusSnackbar.LENGTH_LONG,
+ )
+ .setText(view.context.getString(text))
+ .setAction(view.context.getString(action)) { listener.invoke(view) }
+ .show()
+ } else {
+ FocusSnackbar.make(
+ view,
+ duration = FocusSnackbar.LENGTH_SHORT,
+ )
+ .setText(view.context.getString(text))
+ .show()
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/HtmlLoader.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/HtmlLoader.kt
new file mode 100644
index 0000000000..c8b287ad9e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/HtmlLoader.kt
@@ -0,0 +1,121 @@
+/* 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.focus.utils
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.Base64
+import androidx.annotation.DrawableRes
+import androidx.annotation.RawRes
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.nio.charset.StandardCharsets
+
+private const val MINIMUM_DRAWABLE_SIZE_BYTES = 8
+
+// Base64 encodes 3 bytes at a time, make sure we have a multiple of 3 here
+// I don't know what a sensible chunk size is, let's just go with 300b.
+private const val BYTE_ARRAY_READ_SIZE = 3 * 100
+
+/**
+ * HtmlLoader for loading localized content.
+ */
+object HtmlLoader {
+ /**
+ * Load a given (html or css) resource file into a String. The input can contain tokens that will
+ * be replaced with localised strings.
+ *
+ * @param substitutionTable A table of substitions, e.g. %shortMessage% -> "Error loading page..."
+ * Can be null, in which case no substitutions will be made.
+ * @return The file content, with all substitutions having being made.
+ */
+ fun loadResourceFile(
+ context: Context,
+ @RawRes resourceID: Int,
+ substitutionTable: Map,
+ ): String {
+ try {
+ BufferedReader(
+ InputStreamReader(
+ context.resources.openRawResource(resourceID),
+ StandardCharsets.UTF_8,
+ ),
+ ).use { fileReader ->
+ return readFromResource(fileReader, substitutionTable)
+ }
+ } catch (e: IOException) {
+ throw IllegalStateException("Unable to load error page data", e)
+ }
+ }
+
+ /**
+ * Reads from a given resource and returns a String
+ */
+ private fun readFromResource(
+ fileReader: BufferedReader,
+ substitutionTable: Map,
+ ): String {
+ val outputBuffer = StringBuilder()
+ var line = ""
+ while (fileReader.readLine()?.let { line = it } != null) {
+ for ((key, value) in substitutionTable) {
+ line = line.replace(key, value)
+ }
+ outputBuffer.append(line)
+ }
+ return outputBuffer.toString()
+ }
+
+ private val pngHeader = byteArrayOf(-119, 80, 78, 71, 13, 10, 26, 10)
+
+ /**
+ * Loads a given DRAWABLE resource file into a String.
+ */
+ // We are copying the approach BitmapFactory.decodeResource(Resources, int, Options)
+ // uses - you are explicitly allowed to open Drawables, but the method has a @RawRes
+ // annotation (despite officially supporting Drawables).
+ @SuppressLint("ResourceType")
+ fun loadPngAsDataURI(
+ context: Context,
+ @DrawableRes resourceID: Int,
+ ): String {
+ val builder = StringBuilder()
+ builder.append("data:image/png;base64,")
+
+ try {
+ context.resources.openRawResource(resourceID).use { pngInputStream ->
+ return readPngInputStream(pngInputStream, builder)
+ }
+ } catch (e: IOException) {
+ throw IllegalStateException("Unable to load png data")
+ }
+ }
+
+ /**
+ * Reads a PNG input stream and returns a String
+ */
+ private fun readPngInputStream(
+ pngInputStream: InputStream,
+ builder: StringBuilder,
+ ): String {
+ val data = ByteArray(BYTE_ARRAY_READ_SIZE)
+ var bytesRead: Int
+ var headerVerified = false
+ while (pngInputStream.read(data).also { bytesRead = it } > 0) {
+ // Sanity check: lets make sure this is still a png (i.e. make sure the build system
+ // or Android haven't broken / change the image format).
+ if (!headerVerified) {
+ check(bytesRead >= MINIMUM_DRAWABLE_SIZE_BYTES) { "Loaded drawable is improbably small" }
+ for (i in pngHeader.indices) {
+ check(data[i] == pngHeader[i]) { "Invalid png detected" }
+ }
+ headerVerified = true
+ }
+ builder.append(Base64.encodeToString(data, 0, bytesRead, 0))
+ }
+ return builder.toString()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/IntentUtils.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/IntentUtils.kt
new file mode 100644
index 0000000000..296f301166
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/IntentUtils.kt
@@ -0,0 +1,82 @@
+/* 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.focus.utils
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.LabeledIntent
+import android.os.Build
+import android.os.Parcelable
+import mozilla.components.support.utils.ext.queryIntentActivitiesCompat
+
+object IntentUtils {
+
+ /**
+ * Since Android 12 we need to set PendingIntent mutability explicitly, but Android 6 can be the minimum version
+ * This additional requirement improves your app's security.
+ * FLAG_IMMUTABLE -> Flag indicating that the created PendingIntent should be immutable.
+ */
+ fun defaultIntentPendingFlags(buildVersion: Int = Build.VERSION.SDK_INT): Int =
+ if (buildVersion >= Build.VERSION_CODES.M) {
+ PendingIntent.FLAG_IMMUTABLE
+ } else {
+ 0 // No flags. Default behavior.
+ }
+
+ /**
+ * Method for creating an intent chooser but without the current app
+ */
+ fun getIntentChooser(
+ context: Context,
+ intent: Intent,
+ chooserTitle: CharSequence? = null,
+ ): Intent {
+ val chooserIntent: Intent
+ val resolveInfos = context.packageManager.queryIntentActivitiesCompat(intent, 0).toHashSet()
+
+ val excludedComponentNames = resolveInfos
+ .map { it.activityInfo }
+ .filter { it.packageName == context.packageName }
+ .map { ComponentName(it.packageName, it.name) }
+
+ // Starting with Android N we can use Intent.EXTRA_EXCLUDE_COMPONENTS to exclude components
+ // other way we are constrained to use Intent.EXTRA_INITIAL_INTENTS.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ chooserIntent = Intent.createChooser(intent, chooserTitle)
+ .putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, excludedComponentNames.toTypedArray())
+ } else {
+ var targetIntents = resolveInfos
+ .filterNot { it.activityInfo.packageName == context.packageName }
+ .map { resolveInfo ->
+ val activityInfo = resolveInfo.activityInfo
+ val targetIntent = Intent(intent).apply {
+ component = ComponentName(activityInfo.packageName, activityInfo.name)
+ }
+ LabeledIntent(
+ targetIntent,
+ activityInfo.packageName,
+ resolveInfo.labelRes,
+ resolveInfo.icon,
+ )
+ }
+
+ // Sometimes on Android M and below an empty chooser is displayed, problem reported also here
+ // https://issuetracker.google.com/issues/37085761
+ // To fix that we are creating a chooser with an empty intent
+ chooserIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Intent.createChooser(Intent(), chooserTitle)
+ } else {
+ targetIntents = targetIntents.toMutableList()
+ Intent.createChooser(targetIntents.removeAt(0), chooserTitle)
+ }
+ chooserIntent.putExtra(
+ Intent.EXTRA_INITIAL_INTENTS,
+ targetIntents.toTypedArray(),
+ )
+ }
+ return chooserIntent
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/OneShotOnPreDrawListener.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/OneShotOnPreDrawListener.kt
new file mode 100644
index 0000000000..280e50f53e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/OneShotOnPreDrawListener.kt
@@ -0,0 +1,27 @@
+/* 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.focus.utils
+
+import android.view.View
+import android.view.ViewTreeObserver
+
+/**
+ * A OnPreDrawListener implementation that will execute a callback once and then unsubscribe itself.
+ */
+class OneShotOnPreDrawListener (
+ private val view: V,
+ private inline val onPreDraw: (view: V) -> Boolean,
+) : ViewTreeObserver.OnPreDrawListener {
+
+ init {
+ view.viewTreeObserver.addOnPreDrawListener(this)
+ }
+
+ override fun onPreDraw(): Boolean {
+ view.viewTreeObserver.removeOnPreDrawListener(this)
+
+ return onPreDraw(view)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SearchUtils.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SearchUtils.kt
new file mode 100644
index 0000000000..396fc7f320
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SearchUtils.kt
@@ -0,0 +1,19 @@
+/* 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.focus.utils
+
+import android.content.Context
+import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
+import mozilla.components.feature.search.ext.buildSearchUrl
+import org.mozilla.focus.ext.components
+
+object SearchUtils {
+ fun createSearchUrl(context: Context?, text: String): String {
+ val searchEngine = context?.components?.store?.state?.search?.selectedOrDefaultSearchEngine
+ ?: return text
+
+ return searchEngine.buildSearchUrl(text)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Settings.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Settings.kt
new file mode 100644
index 0000000000..3b16490aa4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/Settings.kt
@@ -0,0 +1,507 @@
+/* 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.focus.utils
+
+import android.accessibilityservice.AccessibilityServiceInfo
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.view.accessibility.AccessibilityManager
+import androidx.annotation.VisibleForTesting
+import androidx.preference.PreferenceManager
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
+import mozilla.components.support.ktx.android.content.PreferencesHolder
+import mozilla.components.support.ktx.android.content.booleanPreference
+import org.mozilla.focus.R
+import org.mozilla.focus.cookiebanner.CookieBannerOption
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.searchsuggestions.SearchSuggestionsPreferences
+import org.mozilla.focus.utils.AppConstants.isKlarBuild
+
+/**
+ * A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
+ * This class is designed to have a lot of (simple) functions
+ */
+@Suppress("TooManyFunctions", "LargeClass")
+class Settings(
+ private val context: Context,
+) : PreferencesHolder {
+
+ companion object {
+ // Default value is block cross site cookies.
+ const val DEFAULT_COOKIE_OPTION_INDEX = 3
+ const val NO_VALUE = "no value"
+ }
+
+ private val accessibilityManager =
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager?
+
+ /**
+ * Check each active accessibility service to see if it can perform gestures, if any can,
+ * then it is *likely* a switch service is enabled.
+ */
+ private val switchServiceIsEnabled: Boolean
+ get() {
+ accessibilityManager?.getEnabledAccessibilityServiceList(0)?.let { activeServices ->
+ for (service in activeServices) {
+ if (service.capabilities.and(AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) == 1) {
+ return true
+ }
+ }
+ }
+
+ return false
+ }
+
+ fun createTrackingProtectionPolicy(
+ shouldBlockCookiesValue: String = shouldBlockCookiesValue(),
+ ): EngineSession.TrackingProtectionPolicy {
+ val trackingCategories: MutableList =
+ mutableListOf(EngineSession.TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)
+
+ if (shouldBlockSocialTrackers()) {
+ trackingCategories.add(EngineSession.TrackingProtectionPolicy.TrackingCategory.SOCIAL)
+ }
+ if (shouldBlockAdTrackers()) {
+ trackingCategories.add(EngineSession.TrackingProtectionPolicy.TrackingCategory.AD)
+ }
+ if (shouldBlockAnalyticTrackers()) {
+ trackingCategories.add(EngineSession.TrackingProtectionPolicy.TrackingCategory.ANALYTICS)
+ }
+ if (shouldBlockOtherTrackers()) {
+ trackingCategories.add(EngineSession.TrackingProtectionPolicy.TrackingCategory.CONTENT)
+ }
+
+ val cookiePolicy = getCookiePolicy(shouldBlockCookiesValue)
+
+ return EngineSession.TrackingProtectionPolicy.select(
+ cookiePolicy = cookiePolicy,
+ trackingCategories = trackingCategories.toTypedArray(),
+ strictSocialTrackingProtection = shouldBlockSocialTrackers(),
+ )
+ }
+
+ private fun getCookiePolicy(shouldBlockCookiesValue: String) =
+ when (shouldBlockCookiesValue) {
+ context.getString(R.string.yes) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE
+
+ context.getString(R.string.third_party_tracker) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NON_TRACKERS
+
+ context.getString(R.string.third_party_only) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY
+
+ context.getString(R.string.cross_site) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
+
+ context.getString(R.string.no) -> {
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL
+ }
+
+ NO_VALUE -> {
+ // Ending up here means that the cookie preference has not been yet modified.
+ // We should set it to the default value.
+ setBlockCookiesValue(
+ resources.getStringArray(R.array.cookies_options_entry_values)[DEFAULT_COOKIE_OPTION_INDEX],
+ )
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
+ }
+
+ else -> {
+ // Ending up here means that the cookie preference has already been stored in another locale.
+ // We will have identify the existing option and set the preference to the corresponding value.
+ // See https://github.com/mozilla-mobile/focus-android/issues/5996.
+
+ val cookieOptionIndex =
+ resources.getStringArray(R.array.cookies_options_entries)
+ .asList().indexOf(shouldBlockCookiesValue())
+
+ val correspondingValue =
+ resources.getStringArray(R.array.cookies_options_entry_values).getOrNull(cookieOptionIndex)
+ ?: resources.getStringArray(R.array.cookies_options_entry_values)[DEFAULT_COOKIE_OPTION_INDEX]
+
+ setBlockCookiesValue(correspondingValue)
+
+ // Get the updated cookie policy for the corresponding value
+ when (shouldBlockCookiesValue) {
+ context.getString(R.string.yes) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE
+
+ context.getString(R.string.third_party_tracker) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NON_TRACKERS
+
+ context.getString(R.string.third_party_only) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY
+
+ context.getString(R.string.cross_site) ->
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
+
+ else -> {
+ // Fallback to the default value.
+ EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL
+ }
+ }
+ }
+ }
+
+ fun setupSafeBrowsing(engine: Engine, shouldUseSafeBrowsing: Boolean = shouldUseSafeBrowsing()) {
+ if (shouldUseSafeBrowsing) {
+ engine.settings.safeBrowsingPolicy = arrayOf(EngineSession.SafeBrowsingPolicy.RECOMMENDED)
+ } else {
+ engine.settings.safeBrowsingPolicy = arrayOf(EngineSession.SafeBrowsingPolicy.NONE)
+ }
+ }
+
+ private val resources: Resources = context.resources
+
+ @Deprecated("This is no longer used. Read search engines from BrowserStore instead")
+ val defaultSearchEngineName: String
+ get() = preferences.getString(getPreferenceKey(R.string.pref_key_search_engine), "")!!
+
+ val openLinksInExternalApp: Boolean
+ get() = preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_open_links_in_external_app),
+ false,
+ )
+
+ var isExperimentationEnabled: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.pref_key_studies), !isKlarBuild)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_key_studies), value)
+ .commit()
+ }
+
+ var shouldShowCookieBannerCfr: Boolean
+ get() = preferences.getBoolean(
+ getPreferenceKey(R.string.pref_cfr_visibility_for_cookie_banner),
+ true,
+ )
+ set(value) {
+ preferences.edit()
+ .putBoolean(
+ getPreferenceKey(R.string.pref_cfr_visibility_for_cookie_banner),
+ value,
+ )
+ .apply()
+ }
+
+ var shouldShowCfrForTrackingProtection: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.pref_cfr_visibility_for_tracking_protection), true)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_cfr_visibility_for_tracking_protection), value)
+ .apply()
+ }
+
+ var shouldShowStartBrowsingCfr: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.pref_cfr_visibility_for_start_browsing), true)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_cfr_visibility_for_start_browsing), value)
+ .apply()
+ }
+
+ var isFirstRun: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.firstrun_shown), true)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.firstrun_shown), value)
+ .apply()
+ }
+
+ /**
+ * This is needed for GUI Testing. If the value is not set in the sharePref
+ * the default value will be the one from Nimbus.
+ */
+ var isNewOnboardingEnable: Boolean
+ get() = preferences.getBoolean(
+ getPreferenceKey(R.string.new_onboarding_enabled),
+ FocusNimbus.features.onboarding.value().isEnabled,
+ )
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.new_onboarding_enabled), value)
+ .apply()
+ }
+
+ var shouldShowPrivacySecuritySettingsToolTip: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.pref_tool_tip_privacy_security_settings), true)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_tool_tip_privacy_security_settings), value)
+ .apply()
+ }
+
+ fun shouldEnableRemoteDebugging(): Boolean =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_remote_debugging),
+ false,
+ )
+
+ fun shouldShowSearchSuggestions(): Boolean =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_show_search_suggestions),
+ false,
+ )
+
+ fun shouldBlockWebFonts(): Boolean =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_performance_block_webfonts),
+ false,
+ )
+
+ fun shouldBlockJavaScript(): Boolean =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_performance_block_javascript),
+ false,
+ )
+
+ fun shouldBlockCookiesValue(): String =
+ preferences.getString(
+ getPreferenceKey(
+ R.string
+ .pref_key_performance_enable_cookies,
+ ),
+ NO_VALUE,
+ )!!
+
+ private fun setBlockCookiesValue(newValue: String) {
+ preferences.edit()
+ .putString(getPreferenceKey(R.string.pref_key_performance_enable_cookies), newValue)
+ .apply()
+ }
+
+ fun shouldUseBiometrics(): Boolean =
+ preferences.getBoolean(getPreferenceKey(R.string.pref_key_biometric), false)
+
+ fun shouldUseSecureMode(): Boolean =
+ preferences.getBoolean(getPreferenceKey(R.string.pref_key_secure), false)
+
+ fun setDefaultSearchEngineByName(name: String) {
+ preferences.edit()
+ .putString(getPreferenceKey(R.string.pref_key_search_engine), name)
+ .apply()
+ }
+
+ fun shouldAutocompleteFromShippedDomainList() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_autocomplete_preinstalled),
+ true,
+ )
+
+ fun shouldAutocompleteFromCustomDomainList() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_autocomplete_custom),
+ true,
+ )
+
+ fun shouldBlockAdTrackers() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_ads),
+ true,
+ )
+
+ private fun shouldUseSafeBrowsing() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_safe_browsing),
+ true,
+ )
+
+ fun shouldBlockAnalyticTrackers() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_analytics),
+ true,
+ )
+
+ fun shouldBlockSocialTrackers() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_social),
+ true,
+ )
+
+ fun shouldBlockOtherTrackers() =
+ preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_other3),
+ false,
+ )
+
+ /**
+ * This is automatically inferred based on the current system status. Not a setting in our app.
+ */
+ fun isAccessibilityEnabled() =
+ accessibilityManager?.isTouchExplorationEnabled ?: false || switchServiceIsEnabled
+
+ fun userHasToggledSearchSuggestions(): Boolean =
+ preferences.getBoolean(SearchSuggestionsPreferences.TOGGLED_SUGGESTIONS_PREF, false)
+
+ fun userHasDismissedNoSuggestionsMessage(): Boolean =
+ preferences.getBoolean(SearchSuggestionsPreferences.DISMISSED_NO_SUGGESTIONS_PREF, false)
+
+ fun hasRequestedDesktop() = preferences.getBoolean(
+ getPreferenceKey(R.string.has_requested_desktop),
+ false,
+ )
+
+ fun getAppLaunchCount() = preferences.getInt(
+ getPreferenceKey(R.string.app_launch_count),
+ 0,
+ )
+
+ fun getTotalBlockedTrackersCount() = preferences.getInt(
+ getPreferenceKey(R.string.pref_key_privacy_total_trackers_blocked_count),
+ 0,
+ )
+
+ fun hasSocialBlocked() = preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_social),
+ true,
+ )
+
+ fun hasAdvertisingBlocked() = preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_ads),
+ true,
+ )
+
+ fun hasAnalyticsBlocked() = preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_privacy_block_analytics),
+ true,
+ )
+
+ var lightThemeSelected by booleanPreference(
+ getPreferenceKey(R.string.pref_key_light_theme),
+ false,
+ )
+
+ var darkThemeSelected by booleanPreference(
+ getPreferenceKey(R.string.pref_key_dark_theme),
+ false,
+ )
+
+ var useDefaultThemeSelected by booleanPreference(
+ getPreferenceKey(R.string.pref_key_default_theme),
+ false,
+ )
+
+ /**
+ * Sets Preferred Color scheme based on Dark/Light Theme Settings or Current Configuration
+ */
+ fun getPreferredColorScheme(): PreferredColorScheme {
+ val inDark =
+ (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
+ Configuration.UI_MODE_NIGHT_YES
+ return when {
+ darkThemeSelected -> PreferredColorScheme.Dark
+ lightThemeSelected -> PreferredColorScheme.Light
+ inDark -> PreferredColorScheme.Dark
+ else -> PreferredColorScheme.Light
+ }
+ }
+
+ var shouldUseNimbusPreview: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.pref_key_use_nimbus_preview), false)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_key_use_nimbus_preview), value)
+ .commit()
+ }
+
+ var useProductionRemoteSettingsServer: Boolean
+ get() = preferences.getBoolean(getPreferenceKey(R.string.pref_key_remote_server_prod), true)
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_key_remote_server_prod), value)
+ .commit()
+ }
+
+ fun addSearchWidgetInstalled(count: Int) {
+ val key = getPreferenceKey(R.string.pref_key_search_widget_installed)
+ val newValue = preferences.getInt(key, 0) + count
+ preferences.edit()
+ .putInt(key, newValue)
+ .apply()
+ }
+
+ val searchWidgetInstalled: Boolean
+ get() = 0 < preferences.getInt(
+ getPreferenceKey(R.string.pref_key_search_widget_installed),
+ 0,
+ )
+
+ /**
+ * This is used for promote search widget dialog to appear only at the first data clearing and
+ * at the 5th one.
+ */
+ fun addClearBrowsingSessions(count: Int) {
+ val key = getPreferenceKey(R.string.pref_key_clear_browsing_sessions)
+ val newValue = preferences.getInt(key, 0) + count
+ preferences.edit()
+ .putInt(key, newValue)
+ .apply()
+ }
+
+ fun getClearBrowsingSessions() = preferences.getInt(
+ getPreferenceKey(R.string.pref_key_clear_browsing_sessions),
+ 0,
+ )
+
+ fun getHttpsOnlyMode(): Engine.HttpsOnlyMode {
+ return if (preferences.getBoolean(getPreferenceKey(R.string.pref_key_https_only), true)) {
+ Engine.HttpsOnlyMode.ENABLED
+ } else {
+ Engine.HttpsOnlyMode.DISABLED
+ }
+ }
+
+ /**
+ * This is needed for GUI Testing. If the value is not set in the sharePref
+ * the default value will be the one from Nimbus.
+ */
+ @VisibleForTesting
+ var isCookieBannerEnable: Boolean
+ get() = preferences.getBoolean(
+ getPreferenceKey(R.string.pref_key_cookie_banner_enabled),
+ FocusNimbus.features.cookieBanner.value().isCookieHandlingEnabled,
+ )
+ set(value) {
+ preferences.edit()
+ .putBoolean(getPreferenceKey(R.string.pref_key_cookie_banner_enabled), value)
+ .apply()
+ }
+
+ fun saveCurrentCookieBannerOptionInSharePref(
+ cookieBannerOption: CookieBannerOption,
+ ) {
+ preferences.edit()
+ .putString(
+ context.getString(R.string.pref_key_cookie_banner_settings),
+ context.getString(cookieBannerOption.prefKeyId),
+ ).apply()
+ }
+
+ fun getCurrentCookieBannerOptionFromSharePref(): CookieBannerOption {
+ val optionValue = preferences.getString(
+ context.getString(R.string.pref_key_cookie_banner_settings),
+ context.getString(CookieBannerOption.CookieBannerRejectAll().prefKeyId),
+ )
+ return when (optionValue) {
+ context.getString(CookieBannerOption.CookieBannerDisabled().prefKeyId) ->
+ CookieBannerOption.CookieBannerDisabled()
+ context.getString(CookieBannerOption.CookieBannerRejectAll().prefKeyId) ->
+ CookieBannerOption.CookieBannerRejectAll()
+ else -> CookieBannerOption.CookieBannerDisabled()
+ }
+ }
+
+ private fun getPreferenceKey(resourceId: Int): String =
+ context.getString(resourceId)
+
+ override val preferences: SharedPreferences
+ get() = PreferenceManager.getDefaultSharedPreferences(context)
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SupportUtils.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SupportUtils.kt
new file mode 100644
index 0000000000..dbaf7d84aa
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/SupportUtils.kt
@@ -0,0 +1,133 @@
+/* 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.focus.utils
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.core.content.ContextCompat
+import androidx.core.net.toUri
+import androidx.fragment.app.FragmentActivity
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.feature.customtabs.createCustomTabConfigFromIntent
+import mozilla.components.support.utils.ext.getPackageInfoCompat
+import org.mozilla.focus.BuildConfig
+import org.mozilla.focus.R
+import org.mozilla.focus.activity.CustomTabActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.locale.Locales
+import org.mozilla.focus.state.AppAction
+import java.io.UnsupportedEncodingException
+import java.net.URLEncoder
+import java.util.Locale
+
+object SupportUtils {
+ const val HELP_URL = "https://support.mozilla.org/kb/what-firefox-focus-android"
+ const val FOCUS_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}"
+ const val RATE_APP_URL = "market://details?id=" + BuildConfig.APPLICATION_ID
+ const val DEFAULT_BROWSER_URL = "https://support.mozilla.org/kb/set-firefox-focus-default-browser-android"
+ const val PRIVACY_NOTICE_URL = "https://www.mozilla.org/privacy/firefox-focus/"
+ const val PRIVACY_NOTICE_KLAR_URL = "https://www.mozilla.org/de/privacy/firefox-klar/"
+
+ const val OPEN_WITH_DEFAULT_BROWSER_URL = "https://www.mozilla.org/openGeneralSettings" // Fake URL
+ val manifestoURL: String
+ get() {
+ val langTag = Locales.getLanguageTag(Locale.getDefault())
+ return "https://www.mozilla.org/$langTag/about/manifesto/"
+ }
+
+ enum class SumoTopic(
+ /** The final path segment for a SUMO URL - see {@see #getSumoURLForTopic} */
+ internal val topicStr: String,
+ ) {
+ ADD_SEARCH_ENGINE("add-search-engine"),
+ AUTOCOMPLETE("autofill-domain-android"),
+ TRACKERS("trackers"),
+ USAGE_DATA("usage-data"),
+ SEARCH_SUGGESTIONS("search-suggestions-focus-android"),
+ ALLOWLIST("focus-android-allowlist"),
+ STUDIES("how-opt-out-studies-firefox-focus-android"),
+ HTTPS_ONLY("https-only-prefs-focus"),
+ COOKIE_BANNER("cookie-banner-reduction-firefox-focus-android"),
+ }
+
+ fun getGenericSumoURLForTopic(topic: SumoTopic): String {
+ val escapedTopic = getEncodedTopicUTF8(topic.topicStr)
+ val langTag = Locales.getLanguageTag(Locale.getDefault())
+ return "https://support.mozilla.org/$langTag/kb/$escapedTopic"
+ }
+
+ /**
+ * Returns the SUMO URL for a specific topic
+ */
+ fun getSumoURLForTopic(appVersion: String, topic: SumoTopic): String {
+ val escapedTopic = getEncodedTopicUTF8(topic.topicStr)
+ val osTarget = "Android"
+ val langTag = Locales.getLanguageTag(Locale.getDefault())
+ return "https://support.mozilla.org/1/mobile/$appVersion/$osTarget/$langTag/$escapedTopic"
+ }
+
+ // For some reason this URL has a different format than the other SUMO URLs
+ fun getSafeBrowsingURL(): String {
+ val langTag = Locales.getLanguageTag(Locale.getDefault())
+ return "https://support.mozilla.org/$langTag/kb/how-does-phishing-and-malware-protection-work"
+ }
+
+ private fun getEncodedTopicUTF8(topic: String): String {
+ try {
+ return URLEncoder.encode(topic, "UTF-8")
+ } catch (e: UnsupportedEncodingException) {
+ throw IllegalStateException("utf-8 should always be available", e)
+ }
+ }
+
+ /**
+ * Returns the version name of this package.
+ */
+ fun getAppVersion(context: Context): String {
+ try {
+ return context.packageManager.getPackageInfoCompat(context.packageName, 0).versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ // This should be impossible - we should always be able to get information about ourselves:
+ throw IllegalStateException("Unable find package details for Focus", e)
+ }
+ }
+
+ fun openDefaultBrowserSumoPage(context: Context) {
+ val tabId = context.components.tabsUseCases.addTab(
+ DEFAULT_BROWSER_URL,
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+
+ context.components.appStore.dispatch(
+ AppAction.OpenTab(tabId),
+ )
+ }
+
+ fun openUrlInCustomTab(activity: FragmentActivity, destinationUrl: String) {
+ activity.intent.putExtra(
+ CustomTabsIntent.EXTRA_TOOLBAR_COLOR,
+ ContextCompat.getColor(activity, R.color.settings_background),
+ )
+
+ val tabId = activity.components.customTabsUseCases.add(
+ url = destinationUrl,
+ customTabConfig = createCustomTabConfigFromIntent(activity.intent, activity.resources),
+ private = true,
+ source = SessionState.Source.Internal.None,
+ )
+ val openCustomTabActivityIntent =
+ Intent(activity, CustomTabActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ data = getSumoURLForTopic(getAppVersion(activity), SumoTopic.ADD_SEARCH_ENGINE).toUri()
+ putExtra(CustomTabActivity.CUSTOM_TAB_ID, tabId)
+ }
+
+ activity.startActivity(openCustomTabActivityIntent)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ViewUtils.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ViewUtils.kt
new file mode 100644
index 0000000000..1733cfc307
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/utils/ViewUtils.kt
@@ -0,0 +1,103 @@
+/* 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.focus.utils
+
+import android.app.Activity
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.view.MenuItem
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import androidx.annotation.StringRes
+import com.google.android.material.snackbar.Snackbar
+import org.mozilla.focus.ext.tryAsActivity
+import java.lang.ref.WeakReference
+
+object ViewUtils {
+
+ private const val MENU_ITEM_ALPHA_ENABLED = 255
+ private const val MENU_ITEM_ALPHA_DISABLED = 130
+
+ /**
+ * Runnable to show the keyboard for a specific view.
+ */
+ @Suppress("ReturnCount")
+ private class ShowKeyboard(view: View?) : Runnable {
+ companion object {
+ private const val INTERVAL_MS = 100
+ private const val MAX_TRIES = 10
+ }
+
+ private val viewReference: WeakReference = WeakReference(view)
+ private val handler: Handler = Handler(Looper.getMainLooper())
+ private var tries: Int = MAX_TRIES
+
+ override fun run() {
+ val myView = viewReference.get() ?: return
+ val activity: Activity = myView.context?.tryAsActivity() ?: return
+ val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager ?: return
+
+ when {
+ tries <= 0 -> return
+ !myView.isFocusable -> return
+ !myView.isFocusableInTouchMode -> return
+ !myView.requestFocus() -> {
+ post()
+ return
+ }
+ !imm.isActive(myView) -> {
+ post()
+ return
+ }
+ !imm.showSoftInput(myView, InputMethodManager.SHOW_IMPLICIT) -> {
+ post()
+ }
+ }
+ }
+
+ fun post() {
+ tries--
+ handler.postDelayed(this, INTERVAL_MS.toLong())
+ }
+ }
+
+ fun showKeyboard(view: View?) {
+ val showKeyboard = ShowKeyboard(view)
+ showKeyboard.post()
+ }
+
+ fun hideKeyboard(view: View?) {
+ val imm = view?.context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ imm.hideSoftInputFromWindow(view.windowToken, 0)
+ }
+
+ /**
+ * Create a custom FocusSnackbar.
+ */
+ fun showBrandedSnackbar(view: View?, @StringRes resId: Int, delayMillis: Int) {
+ val context = view!!.context
+ val snackbar = FocusSnackbar.make(view, Snackbar.LENGTH_LONG)
+ snackbar.setText(context.getString(resId))
+
+ view.postDelayed({ snackbar.show() }, delayMillis.toLong())
+ }
+
+ /**
+ * Enable or disable a [MenuItem]
+ * If the menu item is disabled it can not be clicked and the menu icon is semi-transparent
+ *
+ * @param menuItem the menu item to enable/disable
+ * @param enabled true if the menu item should be enabled
+ */
+ fun setMenuItemEnabled(menuItem: MenuItem, enabled: Boolean) {
+ menuItem.isEnabled = enabled
+ val icon = menuItem.icon
+ if (icon != null) {
+ icon.mutate().alpha = if (enabled) MENU_ITEM_ALPHA_ENABLED else MENU_ITEM_ALPHA_DISABLED
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/AboutPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/AboutPreference.kt
new file mode 100644
index 0000000000..384b3d38ad
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/AboutPreference.kt
@@ -0,0 +1,24 @@
+/* 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.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.preference.Preference
+import org.mozilla.focus.R
+
+/**
+ * Custom preference used to display "About Firefox Focus" in Mozilla settings screen.
+ */
+class AboutPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet,
+ defStyleAttr: Int = 0,
+) : Preference(context, attrs, defStyleAttr) {
+ init {
+ val appName = getContext().resources.getString(R.string.app_name)
+ val title = getContext().resources.getString(R.string.preference_about, appName)
+ setTitle(title)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/CookiesPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/CookiesPreference.kt
new file mode 100644
index 0000000000..699f233e2b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/CookiesPreference.kt
@@ -0,0 +1,46 @@
+/* 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.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceViewHolder
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.settings
+
+/**
+ * Autocomplete preference that will show a sub screen to configure the autocomplete behavior.
+ */
+class CookiesPreference(context: Context, attrs: AttributeSet?) : ListPreference(context, attrs) {
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ updateSummary()
+ showIcon(holder)
+ }
+
+ override fun notifyChanged() {
+ super.notifyChanged()
+ updateSummary()
+ }
+
+ fun updateSummary() {
+ val settings = context.settings
+
+ val cookieOptionIndex =
+ context.resources.getStringArray(R.array.cookies_options_entry_values)
+ .asList().indexOf(settings.shouldBlockCookiesValue())
+ this.summary =
+ context.resources.getStringArray(R.array.cookies_options_entries)[cookieOptionIndex]
+ }
+
+ private fun showIcon(holder: PreferenceViewHolder?) {
+ val widgetFrame: View? = holder?.findViewById(android.R.id.widget_frame)
+ widgetFrame?.isVisible = true
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/DefaultBrowserPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/DefaultBrowserPreference.kt
new file mode 100644
index 0000000000..7b25858209
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/DefaultBrowserPreference.kt
@@ -0,0 +1,90 @@
+/* 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.focus.widget
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.os.Build
+import android.util.AttributeSet
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.google.android.material.switchmaterial.SwitchMaterial
+import mozilla.components.support.utils.Browsers
+import mozilla.components.support.utils.ext.navigateToDefaultBrowserAppsSettings
+import org.mozilla.focus.GleanMetrics.SetDefaultBrowser
+import org.mozilla.focus.R
+import org.mozilla.focus.ext.tryAsActivity
+import org.mozilla.focus.utils.SupportUtils.openDefaultBrowserSumoPage
+
+class DefaultBrowserPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : Preference(context, attrs, defStyleAttr) {
+
+ private var switchView: SwitchMaterial? = null
+ private val browsers
+ get() = Browsers.all(context)
+
+ init {
+ widgetLayoutResource = R.layout.preference_default_browser
+ val appName = context.resources.getString(R.string.app_name)
+ val title = context.resources.getString(R.string.preference_default_browser2, appName)
+ setTitle(title)
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ switchView = holder.findViewById(R.id.switch_widget) as SwitchMaterial
+ update()
+ }
+
+ fun update() {
+ switchView?.isChecked = browsers.isDefaultBrowser
+ }
+
+ public override fun onClick() {
+ val isDefault = browsers.isDefaultBrowser
+ when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
+ context.getSystemService(RoleManager::class.java).also {
+ if (it.isRoleAvailable(RoleManager.ROLE_BROWSER) && !it.isRoleHeld(
+ RoleManager.ROLE_BROWSER,
+ )
+ ) {
+ context.tryAsActivity()?.startActivityForResult(
+ it.createRequestRoleIntent(RoleManager.ROLE_BROWSER),
+ REQUEST_CODE_BROWSER_ROLE,
+ )
+ SetDefaultBrowser.fromAppSettings.record(
+ SetDefaultBrowser.FromAppSettingsExtra(
+ isDefault,
+ ),
+ )
+ } else {
+ context.navigateToDefaultBrowserAppsSettings()
+ SetDefaultBrowser.fromOsSettings.record(SetDefaultBrowser.FromOsSettingsExtra(isDefault))
+ }
+ }
+ }
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
+ context.navigateToDefaultBrowserAppsSettings()
+ SetDefaultBrowser.fromOsSettings.record(SetDefaultBrowser.FromOsSettingsExtra(isDefault))
+ }
+ else -> {
+ openDefaultBrowserSumoPage(context)
+ SetDefaultBrowser.learnMoreOpened.record(
+ SetDefaultBrowser.LearnMoreOpenedExtra(
+ isDefault,
+ ),
+ )
+ }
+ }
+ }
+
+ companion object {
+ const val REQUEST_CODE_BROWSER_ROLE = 1
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/LocaleListPreference.java b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/LocaleListPreference.java
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/MozillaPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/MozillaPreference.kt
new file mode 100644
index 0000000000..f1e11d6c41
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/MozillaPreference.kt
@@ -0,0 +1,21 @@
+/* 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.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.preference.Preference
+import org.mozilla.focus.R
+
+class MozillaPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int = 0,
+) : Preference(context, attrs, defStyleAttr) {
+ init {
+ val appName = getContext().resources.getString(R.string.app_name)
+ val summary = getContext().resources.getString(R.string.preference_mozilla_summary, appName)
+ setSummary(summary)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardCoordinatorLayout.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardCoordinatorLayout.kt
new file mode 100644
index 0000000000..4286d74695
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardCoordinatorLayout.kt
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+package org.mozilla.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+
+/**
+ * A CoordinatorLayout implementation that resizes dynamically based on whether a keyboard is visible or not.
+ */
+class ResizableKeyboardCoordinatorLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : CoordinatorLayout(context, attrs, defStyleAttr) {
+ private val delegate = ResizableKeyboardViewDelegate(this, attrs)
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ delegate.onAttachedToWindow()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ delegate.onDetachedFromWindow()
+ }
+
+ override fun requestDisallowInterceptTouchEvent(b: Boolean) {
+ // As this is a direct parent of EngineView, we don't want to propagate this request to the parent
+ // because that would prevent the hiding of the toolbar.
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardLinearLayout.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardLinearLayout.kt
new file mode 100644
index 0000000000..0297ef663e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardLinearLayout.kt
@@ -0,0 +1,29 @@
+/* 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.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+/**
+ * A CoordinatorLayout implementation that resizes dynamically based on whether a keyboard is visible or not.
+ */
+class ResizableKeyboardLinearLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : LinearLayout(context, attrs, defStyleAttr) {
+ private val delegate = ResizableKeyboardViewDelegate(this, attrs)
+
+ public override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ delegate.onAttachedToWindow()
+ }
+
+ public override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ delegate.onDetachedFromWindow()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardViewDelegate.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardViewDelegate.kt
new file mode 100644
index 0000000000..7dcdbc863f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/ResizableKeyboardViewDelegate.kt
@@ -0,0 +1,119 @@
+/* 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.focus.widget
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewTreeObserver
+import org.mozilla.focus.R
+
+private const val ANIMATION_LENGTH_MS = 200L
+
+/**
+ * A helper class to implement a ViewGroup that resizes dynamically (by adding padding to the bottom)
+ * based on whether a keyboard is visible or not.
+ *
+ * Implementation based on:
+ * https://github.com/mikepenz/MaterialDrawer/blob/master/library/src/main/java/com/mikepenz/materialdrawer/util/KeyboardUtil.java
+ *
+ * A View using this delegate needs to forward the calls to onAttachedToWindow() and onDetachedFromWindow()
+ * to this class.
+ */
+internal class ResizableKeyboardViewDelegate(
+ private val delegateView: View,
+ attrs: AttributeSet?,
+) {
+ private val rect = Rect()
+ private var decorView: View? = null
+ private var shouldAnimate = false
+ private var isAnimating = false
+ private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
+ if (isAnimating) {
+ return@OnGlobalLayoutListener
+ }
+ val difference = calculateDifferenceBetweenHeightAndUsableArea()
+
+ // If difference > 0, keyboard is showing.
+ // If difference =< 0, keyboard is not showing or is in multiview mode.
+ if (difference > 0) {
+ // Keyboard showing -> Set difference has bottom padding.
+ if (delegateView.paddingBottom != difference) {
+ updateBottomPadding(difference)
+ }
+ } else {
+ // Keyboard not showing -> Reset bottom padding.
+ if (delegateView.paddingBottom != 0) {
+ updateBottomPadding(0)
+ }
+ }
+ }
+
+ init {
+ val styleAttributeArray = delegateView.context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.ResizableKeyboardViewDelegate,
+ 0,
+ 0,
+ )
+ shouldAnimate = try {
+ styleAttributeArray.getBoolean(R.styleable.ResizableKeyboardViewDelegate_animate, false)
+ } finally {
+ styleAttributeArray.recycle()
+ }
+ }
+
+ fun onAttachedToWindow() {
+ delegateView.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
+ }
+
+ fun onDetachedFromWindow() {
+ delegateView.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
+ }
+
+ private fun updateBottomPadding(value: Int) {
+ if (shouldAnimate) {
+ animateBottomPaddingTo(value)
+ } else {
+ delegateView.setPadding(0, 0, 0, value)
+ }
+ }
+
+ private fun animateBottomPaddingTo(value: Int) {
+ isAnimating = true
+ val animator = ValueAnimator.ofInt(delegateView.paddingBottom, value)
+ animator.addUpdateListener { animation: ValueAnimator ->
+ delegateView.setPadding(
+ 0,
+ 0,
+ 0,
+ animation.animatedValue as Int,
+ )
+ }
+ animator.duration = ANIMATION_LENGTH_MS
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isAnimating = false
+ }
+ },
+ )
+ animator.start()
+ }
+
+ private fun calculateDifferenceBetweenHeightAndUsableArea(): Int {
+ if (decorView == null) {
+ decorView = delegateView.rootView
+ }
+ decorView!!.getWindowVisibleDisplayFrame(rect)
+ return if (rect.height() >= rect.width()) {
+ delegateView.resources.displayMetrics.heightPixels - rect.bottom
+ } else {
+ delegateView.resources.displayMetrics.widthPixels - rect.right
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/SwitchWithDescription.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/SwitchWithDescription.kt
new file mode 100644
index 0000000000..d726fa20f3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/SwitchWithDescription.kt
@@ -0,0 +1,72 @@
+/* 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.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.withStyledAttributes
+import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
+import org.mozilla.focus.R
+import org.mozilla.focus.databinding.SwitchWithDescriptionBinding
+
+class SwitchWithDescription @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ internal var binding: SwitchWithDescriptionBinding
+
+ init {
+ val view =
+ LayoutInflater.from(context).inflate(R.layout.switch_with_description, this, true)
+ binding = SwitchWithDescriptionBinding.bind(view)
+
+ context.withStyledAttributes(attrs, R.styleable.SwitchWithDescription, defStyleAttr, 0) {
+ val icon = getResourceId(
+ R.styleable.SwitchWithDescription_switchIcon,
+ R.drawable.mozac_ic_shield_24,
+ )
+
+ val switchTitle = resources.getString(
+ getResourceId(
+ R.styleable.SwitchWithDescription_switchTitle,
+ R.string.enhanced_tracking_protection,
+ ),
+ )
+ updateIcon(icon = icon, iconContentDescription = switchTitle)
+ updateTitle(switchTitle)
+
+ updateDescription(
+ resources.getString(
+ getResourceId(
+ R.styleable.SwitchWithDescription_switchDescription,
+ R.string.enhanced_tracking_protection_state_on,
+ ),
+ ),
+ )
+ }
+ }
+
+ private fun updateTitle(title: String) {
+ binding.title.text = title
+ }
+
+ internal fun updateDescription(description: String) {
+ binding.description.text = description
+ }
+
+ internal fun updateIcon(icon: Int, iconContentDescription: String) {
+ with(binding.switchWidget) {
+ putCompoundDrawablesRelativeWithIntrinsicBounds(
+ start = AppCompatResources.getDrawable(context, icon),
+ )
+ contentDescription = iconContentDescription
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/TelemetrySwitchPreference.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/TelemetrySwitchPreference.kt
new file mode 100644
index 0000000000..10ecb00236
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/widget/TelemetrySwitchPreference.kt
@@ -0,0 +1,44 @@
+/* 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.focus.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import mozilla.components.service.glean.Glean
+import org.mozilla.focus.R
+import org.mozilla.focus.settings.LearnMoreSwitchPreference
+import org.mozilla.focus.telemetry.GleanMetricsService
+import org.mozilla.focus.utils.SupportUtils
+
+/**
+ * Switch preference for enabling/disabling telemetry
+ */
+internal class TelemetrySwitchPreference(context: Context, attrs: AttributeSet?) :
+ LearnMoreSwitchPreference(context, attrs) {
+
+ init {
+ isChecked = GleanMetricsService.isTelemetryEnabled(context)
+ }
+
+ override fun onClick() {
+ super.onClick()
+
+ Glean.setUploadEnabled(isChecked)
+ }
+
+ override fun getDescription(): String {
+ return context.resources.getString(
+ R.string.preference_mozilla_telemetry_summary2,
+ context.resources.getString(R.string.app_name),
+ )
+ }
+
+ override fun getLearnMoreUrl(): String {
+ return SupportUtils.getSumoURLForTopic(
+ SupportUtils.getAppVersion(context),
+ SupportUtils.SumoTopic.USAGE_DATA,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/res/anim/erase_animation.xml b/mobile/android/focus-android/app/src/main/res/anim/erase_animation.xml
new file mode 100644
index 0000000000..120df0a9b6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/anim/erase_animation.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/anim/fab_reveal.xml b/mobile/android/focus-android/app/src/main/res/anim/fab_reveal.xml
new file mode 100644
index 0000000000..26aac87a7b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/anim/fab_reveal.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/anim/fade_in.xml b/mobile/android/focus-android/app/src/main/res/anim/fade_in.xml
new file mode 100644
index 0000000000..6c96876a62
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/anim/fade_in.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/anim/fade_out.xml b/mobile/android/focus-android/app/src/main/res/anim/fade_out.xml
new file mode 100644
index 0000000000..c247c63616
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/anim/fade_out.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/color/preference_title_text.xml b/mobile/android/focus-android/app/src/main/res/color/preference_title_text.xml
new file mode 100644
index 0000000000..19d8f969a2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/color/preference_title_text.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/color/selected_search_engine_state.xml b/mobile/android/focus-android/app/src/main/res/color/selected_search_engine_state.xml
new file mode 100644
index 0000000000..a5d05957f6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/color/selected_search_engine_state.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget.png b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget.png
new file mode 100644
index 0000000000..2f967151f4
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget_promote_dialog.png b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget_promote_dialog.png
new file mode 100644
index 0000000000..e785788998
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_search_widget_promote_dialog.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_snackbar_background.xml b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_snackbar_background.xml
new file mode 100644
index 0000000000..229d8793b3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/focus_snackbar_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img1.png b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img1.png
new file mode 100644
index 0000000000..93d8e85efa
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img1.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img2.png b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img2.png
new file mode 100644
index 0000000000..daf2c89711
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img2.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img3.png b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img3.png
new file mode 100644
index 0000000000..e7f49dabdb
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img3.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img4.png b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img4.png
new file mode 100644
index 0000000000..3209bcd181
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-hdpi/onboarding_img4.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-land-night/home_background.xml b/mobile/android/focus-android/app/src/main/res/drawable-land-night/home_background.xml
new file mode 100644
index 0000000000..dcb440442c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable-land-night/home_background.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-land/dark_background.xml b/mobile/android/focus-android/app/src/main/res/drawable-land/dark_background.xml
new file mode 100644
index 0000000000..ae0d91099b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable-land/dark_background.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-land/home_background.xml b/mobile/android/focus-android/app/src/main/res/drawable-land/home_background.xml
new file mode 100644
index 0000000000..014e33d8c2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable-land/home_background.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget.png b/mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget.png
new file mode 100644
index 0000000000..ddf77786d8
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget_promote_dialog.png b/mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget_promote_dialog.png
new file mode 100644
index 0000000000..60dceda0b1
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-night-hdpi/focus_search_widget_promote_dialog.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-night/home_background.xml b/mobile/android/focus-android/app/src/main/res/drawable-night/home_background.xml
new file mode 100644
index 0000000000..dfa5e7b1d9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable-night/home_background.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-nodpi/ic_homescreen_shape.png b/mobile/android/focus-android/app/src/main/res/drawable-nodpi/ic_homescreen_shape.png
new file mode 100644
index 0000000000..b7f30678d4
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-nodpi/ic_homescreen_shape.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-v24/ic_splash_screen.xml b/mobile/android/focus-android/app/src/main/res/drawable-v24/ic_splash_screen.xml
new file mode 100644
index 0000000000..496cfae60f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable-v24/ic_splash_screen.xml
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img1.png b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img1.png
new file mode 100644
index 0000000000..061154a03d
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img1.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img2.png b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img2.png
new file mode 100644
index 0000000000..86f4b03caf
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img2.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img3.png b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img3.png
new file mode 100644
index 0000000000..ab28b46973
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img3.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img4.png b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img4.png
new file mode 100644
index 0000000000..0586a7c40c
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xhdpi/onboarding_img4.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img1.png b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img1.png
new file mode 100644
index 0000000000..326fd394b1
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img1.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img2.png b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img2.png
new file mode 100644
index 0000000000..4b0e98b70b
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img2.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img3.png b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img3.png
new file mode 100644
index 0000000000..f5fe101f17
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img3.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img4.png b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img4.png
new file mode 100644
index 0000000000..c2f377a56c
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable-xxhdpi/onboarding_img4.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_gradient.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_gradient.xml
new file mode 100644
index 0000000000..79a9e23abf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_gradient.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_install_banner.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_install_banner.xml
new file mode 100644
index 0000000000..bfddde2656
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_install_banner.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_list_item_current_session.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_list_item_current_session.xml
new file mode 100644
index 0000000000..4935c3eee8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_list_item_current_session.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_list_item_session.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_list_item_session.xml
new file mode 100644
index 0000000000..f7997c2c1d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_list_item_session.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_open_in_item.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_open_in_item.xml
new file mode 100644
index 0000000000..cbac869545
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_open_in_item.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_search_suggestion_section.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_search_suggestion_section.xml
new file mode 100644
index 0000000000..fdacddfd94
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_search_suggestion_section.xml
@@ -0,0 +1,12 @@
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/background_snackbar.xml b/mobile/android/focus-android/app/src/main/res/drawable/background_snackbar.xml
new file mode 100644
index 0000000000..7372f0b34d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/background_snackbar.xml
@@ -0,0 +1,19 @@
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/context_menu_navigation_view_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/context_menu_navigation_view_background.xml
new file mode 100644
index 0000000000..39e8332693
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/context_menu_navigation_view_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/dark_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/dark_background.xml
new file mode 100644
index 0000000000..0fd67d7e5c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/dark_background.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/dialog_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/dialog_background.xml
new file mode 100644
index 0000000000..55be8c4163
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/dialog_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/dialog_warning_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/dialog_warning_background.xml
new file mode 100644
index 0000000000..7b914a6186
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/dialog_warning_background.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/find_in_page_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/find_in_page_background.xml
new file mode 100644
index 0000000000..4a472d8342
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/find_in_page_background.xml
@@ -0,0 +1,18 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/firstrun_button_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/firstrun_button_background.xml
new file mode 100644
index 0000000000..4ef7bded47
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/firstrun_button_background.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/foreground_list_item_erase.xml b/mobile/android/focus-android/app/src/main/res/drawable/foreground_list_item_erase.xml
new file mode 100644
index 0000000000..2f3d2bb4cf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/foreground_list_item_erase.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/highlight_dot.xml b/mobile/android/focus-android/app/src/main/res/drawable/highlight_dot.xml
new file mode 100644
index 0000000000..23e9de740d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/highlight_dot.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/home_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/home_background.xml
new file mode 100644
index 0000000000..53ccc626c9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/home_background.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_down.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_down.xml
new file mode 100644
index 0000000000..4f59ba2181
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_down.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_up.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_up.xml
new file mode 100644
index 0000000000..aa16154a57
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_arrowhead_up.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_autoplay_enabled.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_autoplay_enabled.xml
new file mode 100644
index 0000000000..439947641d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_autoplay_enabled.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_back_button.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_back_button.xml
new file mode 100644
index 0000000000..5194f6f762
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_back_button.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_camera_enabled.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_camera_enabled.xml
new file mode 100644
index 0000000000..d3c49be5a6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_camera_enabled.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_check.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_check.xml
new file mode 100644
index 0000000000..0eb0456194
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_check.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_cookies_disable.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_cookies_disable.xml
new file mode 100644
index 0000000000..a08a26fa1b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_cookies_disable.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_developer.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_developer.xml
new file mode 100644
index 0000000000..c6fb44178f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_developer.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_download.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_download.xml
new file mode 100644
index 0000000000..a5353f0c71
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_download.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_error_session_crashed.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_error_session_crashed.xml
new file mode 100644
index 0000000000..8421a7052f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_error_session_crashed.xml
@@ -0,0 +1,387 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_favorite.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_favorite.xml
new file mode 100644
index 0000000000..148fe6fe06
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_favorite.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_fingerprint.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_fingerprint.xml
new file mode 100644
index 0000000000..42df9f0ad7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_fingerprint.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_firefox.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_firefox.xml
new file mode 100644
index 0000000000..920b76621d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_firefox.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_info.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_info.xml
new file mode 100644
index 0000000000..33a86afb6a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_info.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_internet.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_internet.xml
new file mode 100644
index 0000000000..f2d89b25b0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_internet.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_language.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_language.xml
new file mode 100644
index 0000000000..d24cc9d3a3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_language.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..bfb274324c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_foreground.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..496cfae60f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_link.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_link.xml
new file mode 100644
index 0000000000..3ff3bf7ef2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_link.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_menu.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_menu.xml
new file mode 100644
index 0000000000..6bc47fb6f5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_menu.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_mozilla.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_mozilla.xml
new file mode 100644
index 0000000000..c0b957a540
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_mozilla.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_notification.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 0000000000..edd79aeb01
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_reorder.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_reorder.xml
new file mode 100644
index 0000000000..a96a38b801
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_reorder.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_shortcut_erase.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_shortcut_erase.xml
new file mode 100644
index 0000000000..9626a66b92
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_shortcut_erase.xml
@@ -0,0 +1,16 @@
+
+
+
+ -
+
+
+
+
+ -
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_splash_screen.png b/mobile/android/focus-android/app/src/main/res/drawable/ic_splash_screen.png
new file mode 100644
index 0000000000..a2446f4439
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable/ic_splash_screen.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/ic_tab_new.xml b/mobile/android/focus-android/app/src/main/res/drawable/ic_tab_new.xml
new file mode 100644
index 0000000000..b29067c10c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/ic_tab_new.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding.xml b/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding.xml
new file mode 100644
index 0000000000..e7b0b8f6f5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_default.xml b/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_default.xml
new file mode 100644
index 0000000000..811e759466
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_default.xml
@@ -0,0 +1,15 @@
+
+
+
+ -
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_selected.xml b/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_selected.xml
new file mode 100644
index 0000000000..b8667ec7c2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/indicator_onboarding_selected.xml
@@ -0,0 +1,15 @@
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/menu_item_dark_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/menu_item_dark_background.xml
new file mode 100644
index 0000000000..360b8d77ee
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/menu_item_dark_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/mozac_ic_broken_lock.xml b/mobile/android/focus-android/app/src/main/res/drawable/mozac_ic_broken_lock.xml
new file mode 100644
index 0000000000..dc9ec5a9f4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/mozac_ic_broken_lock.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/onboarding_logo.xml b/mobile/android/focus-android/app/src/main/res/drawable/onboarding_logo.xml
new file mode 100644
index 0000000000..1c3ed32928
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/onboarding_logo.xml
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/onboarding_second_screen_icon.png b/mobile/android/focus-android/app/src/main/res/drawable/onboarding_second_screen_icon.png
new file mode 100644
index 0000000000..6c02b1c1f4
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/drawable/onboarding_second_screen_icon.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/photon_progressbar.xml b/mobile/android/focus-android/app/src/main/res/drawable/photon_progressbar.xml
new file mode 100644
index 0000000000..800c76dccd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/photon_progressbar.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/preference_foreground_disabled.xml b/mobile/android/focus-android/app/src/main/res/drawable/preference_foreground_disabled.xml
new file mode 100644
index 0000000000..baaab7dd73
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/preference_foreground_disabled.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/preference_multiselect_search_engine_foreground.xml b/mobile/android/focus-android/app/src/main/res/drawable/preference_multiselect_search_engine_foreground.xml
new file mode 100644
index 0000000000..749e60cf15
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/preference_multiselect_search_engine_foreground.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/scrollbar_thumb.xml b/mobile/android/focus-android/app/src/main/res/drawable/scrollbar_thumb.xml
new file mode 100644
index 0000000000..b0e2268596
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/scrollbar_thumb.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/tab_number_border.xml b/mobile/android/focus-android/app/src/main/res/drawable/tab_number_border.xml
new file mode 100644
index 0000000000..53a16d90d9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/tab_number_border.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/toolbar_url_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/toolbar_url_background.xml
new file mode 100644
index 0000000000..df15d1eac6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/toolbar_url_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/top_rounded_corners_bottom_sheet_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/top_rounded_corners_bottom_sheet_background.xml
new file mode 100644
index 0000000000..ea1b948a52
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/top_rounded_corners_bottom_sheet_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/urlbar_background.xml b/mobile/android/focus-android/app/src/main/res/drawable/urlbar_background.xml
new file mode 100644
index 0000000000..c00fb2e012
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/urlbar_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/drawable/wordmark2.xml b/mobile/android/focus-android/app/src/main/res/drawable/wordmark2.xml
new file mode 100644
index 0000000000..07319181c1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/drawable/wordmark2.xml
@@ -0,0 +1,232 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis.xml b/mobile/android/focus-android/app/src/main/res/font/metropolis.xml
new file mode 100644
index 0000000000..9fc50a8f90
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/font/metropolis.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_black.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_black.ttf
new file mode 100644
index 0000000000..ba26a9da8f
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_black.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_blackitalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_blackitalic.ttf
new file mode 100644
index 0000000000..7dabcf8e04
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_blackitalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_bold.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_bold.ttf
new file mode 100644
index 0000000000..384bc526c3
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_bold.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_bolditalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_bolditalic.ttf
new file mode 100644
index 0000000000..2ce79751d5
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_bolditalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_extrabold.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_extrabold.ttf
new file mode 100644
index 0000000000..743d11b15e
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_extrabold.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_extrabolditalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_extrabolditalic.ttf
new file mode 100644
index 0000000000..45cd9824b2
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_extrabolditalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_extralighitalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_extralighitalic.ttf
new file mode 100644
index 0000000000..09201497b5
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_extralighitalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_extralight.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_extralight.ttf
new file mode 100644
index 0000000000..f977aec971
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_extralight.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_light.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_light.ttf
new file mode 100644
index 0000000000..64a0c83a42
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_light.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_lightitalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_lightitalic.ttf
new file mode 100644
index 0000000000..6f4cfeb81f
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_lightitalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_medium.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_medium.ttf
new file mode 100644
index 0000000000..5d838c66b1
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_medium.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_mediumitalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_mediumitalic.ttf
new file mode 100644
index 0000000000..c5646d24aa
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_mediumitalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_regular.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_regular.ttf
new file mode 100644
index 0000000000..012acd813f
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_regular.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_regularitalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_regularitalic.ttf
new file mode 100644
index 0000000000..60f3502ecb
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_regularitalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_semibold.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_semibold.ttf
new file mode 100644
index 0000000000..2f7522d57c
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_semibold.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_semibolditalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_semibolditalic.ttf
new file mode 100644
index 0000000000..7451471270
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_semibolditalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_thin.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_thin.ttf
new file mode 100644
index 0000000000..f1ca33732c
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_thin.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/font/metropolis_thinitalic.ttf b/mobile/android/focus-android/app/src/main/res/font/metropolis_thinitalic.ttf
new file mode 100644
index 0000000000..d277f65f95
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/font/metropolis_thinitalic.ttf differ
diff --git a/mobile/android/focus-android/app/src/main/res/layout/active_study_item.xml b/mobile/android/focus-android/app/src/main/res/layout/active_study_item.xml
new file mode 100644
index 0000000000..65c8aaf8cf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/active_study_item.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/activity_customtab.xml b/mobile/android/focus-android/app/src/main/res/layout/activity_customtab.xml
new file mode 100644
index 0000000000..da813784aa
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/activity_customtab.xml
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/activity_info.xml b/mobile/android/focus-android/app/src/main/res/layout/activity_info.xml
new file mode 100644
index 0000000000..8755bb2aaf
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/activity_info.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/activity_main.xml b/mobile/android/focus-android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000..352c0fc0e0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/connection_details.xml b/mobile/android/focus-android/app/src/main/res/layout/connection_details.xml
new file mode 100644
index 0000000000..c59ff6da90
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/connection_details.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/cookie_banner_reducer_details.xml b/mobile/android/focus-android/app/src/main/res/layout/cookie_banner_reducer_details.xml
new file mode 100644
index 0000000000..d378b0d434
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/cookie_banner_reducer_details.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/cookies_preference.xml b/mobile/android/focus-android/app/src/main/res/layout/cookies_preference.xml
new file mode 100644
index 0000000000..ff29803a23
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/cookies_preference.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/custom_tab_menu_item.xml b/mobile/android/focus-android/app/src/main/res/layout/custom_tab_menu_item.xml
new file mode 100644
index 0000000000..c0e8e28689
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/custom_tab_menu_item.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/dialog_add_to_homescreen2.xml b/mobile/android/focus-android/app/src/main/res/layout/dialog_add_to_homescreen2.xml
new file mode 100644
index 0000000000..644eae61c8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/dialog_add_to_homescreen2.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml b/mobile/android/focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml
new file mode 100644
index 0000000000..293e975d12
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/dialog_full_screen_notification.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/dialog_tracking_protection_sheet.xml b/mobile/android/focus-android/app/src/main/res/layout/dialog_tracking_protection_sheet.xml
new file mode 100644
index 0000000000..6d31216a10
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/dialog_tracking_protection_sheet.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/firstrun_page.xml b/mobile/android/focus-android/app/src/main/res/layout/firstrun_page.xml
new file mode 100644
index 0000000000..dc34362376
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/firstrun_page.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_preference.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_preference.xml
new file mode 100644
index 0000000000..7766535f81
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_preference.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_preference_category_no_title.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_category_no_title.xml
new file mode 100644
index 0000000000..05b3f61fb9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_category_no_title.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_preference_compose_layout.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_compose_layout.xml
new file mode 100644
index 0000000000..775192e4ee
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_compose_layout.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_preference_left_checkbox.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_left_checkbox.xml
new file mode 100644
index 0000000000..266be4064d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_left_checkbox.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_preference_new_tab.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_new_tab.xml
new file mode 100644
index 0000000000..449cce72b3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_new_tab.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_preference_no_icon.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_no_icon.xml
new file mode 100644
index 0000000000..2a43771ef3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_preference_no_icon.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/focus_snackbar.xml b/mobile/android/focus-android/app/src/main/res/layout/focus_snackbar.xml
new file mode 100644
index 0000000000..27c9437281
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/focus_snackbar.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_about.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_about.xml
new file mode 100644
index 0000000000..ccd80790d1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_about.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_about_libraries.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_about_libraries.xml
new file mode 100644
index 0000000000..8c301b663f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_about_libraries.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_add_domain.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_add_domain.xml
new file mode 100644
index 0000000000..8400731334
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_add_domain.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_customdomains.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_customdomains.xml
new file mode 100644
index 0000000000..2cf339cce2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_autocomplete_customdomains.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_browser.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_browser.xml
new file mode 100644
index 0000000000..296f833810
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_browser.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_crash_reporter.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_crash_reporter.xml
new file mode 100644
index 0000000000..503d4563b4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_crash_reporter.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_exceptions_domains.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_exceptions_domains.xml
new file mode 100644
index 0000000000..7cd5dbd52d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_exceptions_domains.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_firstrun.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_firstrun.xml
new file mode 100644
index 0000000000..074033be3c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_firstrun.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_info.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_info.xml
new file mode 100644
index 0000000000..c35e975205
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_info.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_open_with.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_open_with.xml
new file mode 100644
index 0000000000..8c6e4ce405
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_open_with.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_search_suggestions.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_search_suggestions.xml
new file mode 100644
index 0000000000..081591fca4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_search_suggestions.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_settings.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000000..8471814894
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_studies.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_studies.xml
new file mode 100644
index 0000000000..ca3e67501b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_studies.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/fragment_urlinput.xml b/mobile/android/focus-android/app/src/main/res/layout/fragment_urlinput.xml
new file mode 100644
index 0000000000..2a5f336e62
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/fragment_urlinput.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/item_add_custom_domain.xml b/mobile/android/focus-android/app/src/main/res/layout/item_add_custom_domain.xml
new file mode 100644
index 0000000000..e5e10e80d6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/item_add_custom_domain.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/item_app.xml b/mobile/android/focus-android/app/src/main/res/layout/item_app.xml
new file mode 100644
index 0000000000..012e940f09
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/item_app.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/item_custom_domain.xml b/mobile/android/focus-android/app/src/main/res/layout/item_custom_domain.xml
new file mode 100644
index 0000000000..953e22c68f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/item_custom_domain.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/item_indicator_menu_button.xml b/mobile/android/focus-android/app/src/main/res/layout/item_indicator_menu_button.xml
new file mode 100644
index 0000000000..a534662aa1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/item_indicator_menu_button.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/item_install_banner.xml b/mobile/android/focus-android/app/src/main/res/layout/item_install_banner.xml
new file mode 100644
index 0000000000..d7e091bb2f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/item_install_banner.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/item_session.xml b/mobile/android/focus-android/app/src/main/res/layout/item_session.xml
new file mode 100644
index 0000000000..d7d84be94e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/item_session.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/menu_item.xml b/mobile/android/focus-android/app/src/main/res/layout/menu_item.xml
new file mode 100644
index 0000000000..19d3c688a8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/menu_item.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/menu_navigation.xml b/mobile/android/focus-android/app/src/main/res/layout/menu_navigation.xml
new file mode 100644
index 0000000000..0bec4926c3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/menu_navigation.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/popup_tabs.xml b/mobile/android/focus-android/app/src/main/res/layout/popup_tabs.xml
new file mode 100644
index 0000000000..02306ddcd7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/popup_tabs.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_default_browser.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_default_browser.xml
new file mode 100644
index 0000000000..c97391ed0d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_default_browser.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_manual_add_search_engine.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_manual_add_search_engine.xml
new file mode 100644
index 0000000000..56493ca633
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_manual_add_search_engine.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_radio_button.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_radio_button.xml
new file mode 100644
index 0000000000..c88b35f36c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_radio_button.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_screen_header_layout.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_screen_header_layout.xml
new file mode 100644
index 0000000000..1d763e1496
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_screen_header_layout.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_search_engine_chooser.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_search_engine_chooser.xml
new file mode 100644
index 0000000000..644da9dfb2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_search_engine_chooser.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_section_header_layout.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_section_header_layout.xml
new file mode 100644
index 0000000000..526cb4b007
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_section_header_layout.xml
@@ -0,0 +1,17 @@
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/preference_switch_learn_more.xml b/mobile/android/focus-android/app/src/main/res/layout/preference_switch_learn_more.xml
new file mode 100644
index 0000000000..fcffdde75d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/preference_switch_learn_more.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/search_engine_checkbox_button.xml b/mobile/android/focus-android/app/src/main/res/layout/search_engine_checkbox_button.xml
new file mode 100644
index 0000000000..7b4463e1ae
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/search_engine_checkbox_button.xml
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/search_engine_radio_button.xml b/mobile/android/focus-android/app/src/main/res/layout/search_engine_radio_button.xml
new file mode 100644
index 0000000000..7275caef93
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/search_engine_radio_button.xml
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/studies_section_item.xml b/mobile/android/focus-android/app/src/main/res/layout/studies_section_item.xml
new file mode 100644
index 0000000000..5efbefe5b4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/studies_section_item.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/switch_with_description.xml b/mobile/android/focus-android/app/src/main/res/layout/switch_with_description.xml
new file mode 100644
index 0000000000..6170ef78fe
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/switch_with_description.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/layout/toolbar.xml b/mobile/android/focus-android/app/src/main/res/layout/toolbar.xml
new file mode 100644
index 0000000000..5ca4ecfcf7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/layout/toolbar.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_add.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_add.xml
new file mode 100644
index 0000000000..cbc3acae6a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_add.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_list.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_list.xml
new file mode 100644
index 0000000000..6e4b2f2a5e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_list.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_remove.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_remove.xml
new file mode 100644
index 0000000000..52c30c75d0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_autocomplete_remove.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_exceptions_list.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_exceptions_list.xml
new file mode 100644
index 0000000000..630d3f3fa4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_exceptions_list.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_remove_search_engines.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_remove_search_engines.xml
new file mode 100644
index 0000000000..35848abd98
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_remove_search_engines.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_search_engine_manual_add.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_search_engine_manual_add.xml
new file mode 100644
index 0000000000..045942b074
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_search_engine_manual_add.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/menu/menu_search_engines.xml b/mobile/android/focus-android/app/src/main/res/menu/menu_search_engines.xml
new file mode 100644
index 0000000000..743599f6f5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/menu/menu_search_engines.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/android/focus-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..c7743a9582
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..3241608b37
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..047ad9222f
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..ebc18129a9
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..01169ef157
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..43ea507a94
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..1d6e03c76f
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..e6c4f25d44
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..171320609d
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..522029dbe9
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..19fc10c2cd
Binary files /dev/null and b/mobile/android/focus-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/main/res/raw/about.html b/mobile/android/focus-android/app/src/main/res/raw/about.html
new file mode 100644
index 0000000000..7e24fcbca7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/raw/about.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+%about-version%
+%about-content%
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/raw/gpl.html b/mobile/android/focus-android/app/src/main/res/raw/gpl.html
new file mode 100644
index 0000000000..1af9930b33
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/raw/gpl.html
@@ -0,0 +1,713 @@
+
+
+
+
+
+
+
+ GNU General Public License 3.0
+
+
+
+
+
+
+
+GNU General Public License 3.0
+
+Version 3, 29 June 2007
+
+Copyright © 2007 Free Software Foundation, Inc.
+<http://fsf.org/ >
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+ Preamble
+
+The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+“This License” refers to version 3 of the GNU General Public License.
+
+“Copyright” also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+“The Program” refers to any copyrightable work licensed under this
+License. Each licensee is addressed as “you”. “Licensees” and
+“recipients” may be individuals or organizations.
+
+To “modify” a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a “modified version” of the
+earlier work or a work “based on” the earlier work.
+
+A “covered work” means either the unmodified Program or a work based
+on the Program.
+
+To “propagate” a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+To “convey” a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays “Appropriate Legal Notices”
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+The “source code” for a work means the preferred form of the work
+for making modifications to it. “Object code” means any non-source
+form of a work.
+
+A “Standard Interface” means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+The “System Libraries” of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+“Major Component”, in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+The “Corresponding Source” for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+
+a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ “keep intact all notices”.
+
+c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+
+A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+“aggregate” if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+
+a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+
+A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+A “User Product” is either (1) a “consumer product”, which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, “normally used” refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+“Installation Information” for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+“Additional permissions” are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+
+a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+
+All other non-permissive additional terms are considered “further
+restrictions” within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+An “entity transaction” is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+A “contributor” is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's “contributor version”.
+
+A contributor's “essential patent claims” are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, “control” includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+In the following three paragraphs, a “patent license” is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To “grant” such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. “Knowingly relying” means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+A patent license is “discriminatory” if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License “or any later version” applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the “copyright” line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an “about box”.
+
+You should also get your employer (if you work as a programmer) or school,
+if any, to sign a “copyright disclaimer” for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/ >.
+
+The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html >.
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/raw/initial_experiments.json b/mobile/android/focus-android/app/src/main/res/raw/initial_experiments.json
new file mode 100644
index 0000000000..268c73f0e3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/raw/initial_experiments.json
@@ -0,0 +1,3 @@
+{
+ "data": []
+}
diff --git a/mobile/android/focus-android/app/src/main/res/raw/licenses.html b/mobile/android/focus-android/app/src/main/res/raw/licenses.html
new file mode 100644
index 0000000000..3f90063160
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/raw/licenses.html
@@ -0,0 +1,948 @@
+
+
+
+
+
+
+
+
+
+Licenses
+
+
+ Binaries of this product are made available to you by Mozilla under the Mozilla Public License 2.0 (MPL).
+
+
+
+ More specifically, most of the source code for this product is available under the Mozilla Public License 2.0 (MPL)
+ and Apache License 2.0. Additional software components for this product are available under one of a variety of
+ other free and open source licenses. Those that require reproduction of the license text in the distribution are
+ given below. (Note: your copy of this product may not contain code covered by one or more of the licenses listed
+ here, depending on the exact product and version you choose.)
+
+
+
+
+
+
+
+
+
+
+
+
+ Focus - https://github.com/mozilla-mobile/focus-android
+ Telemetry - https://github.com/mozilla-mobile/telemetry-android
+
+
+ 1. Definitions
+
+ 1.1. “Contributor”
+ means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.
+
+ 1.2. “Contributor Version”
+ means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution.
+
+ 1.3. “Contribution”
+ means Covered Software of a particular Contributor.
+
+ 1.4. “Covered Software”
+ means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.
+
+ 1.5. “Incompatible With Secondary Licenses”
+ means
+
+ that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or
+ that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.
+
+
+ 1.6. “Executable Form”
+ means any form of the work other than Source Code Form.
+
+ 1.7. “Larger Work”
+ means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.
+
+ 1.8. “License”
+ means this document.
+
+ 1.9. “Licensable”
+ means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.
+
+ 1.10. “Modifications”
+ means any of the following:
+
+ any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or
+ any new file in Source Code Form that contains any Covered Software.
+
+
+ 1.11. “Patent Claims” of a Contributor
+ means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.
+
+ 1.12. “Secondary License”
+ means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.
+
+ 1.13. “Source Code Form”
+ means the form of the work preferred for making modifications.
+
+ 1.14. “You” (or “Your”)
+ means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
+
+
+ 2. License Grants and Conditions
+ 2.1. Grants
+ Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+ under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and
+ under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.
+
+ 2.2. Effective Date
+ The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.
+ 2.3. Limitations on Grant Scope
+ The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:
+
+ for any code that a Contributor has removed from Covered Software; or
+ for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or
+ under Patent Claims infringed by Covered Software in the absence of its Contributions.
+
+ This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).
+ 2.4. Subsequent Licenses
+ No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).
+ 2.5. Representation
+ Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.
+ 2.6. Fair Use
+ This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.
+ 2.7. Conditions
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.
+ 3. Responsibilities
+ 3.1. Distribution of Source Form
+ All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form.
+ 3.2. Distribution of Executable Form
+ If You distribute Covered Software in Executable Form then:
+
+ such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and
+ You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License.
+
+ 3.3. Distribution of a Larger Work
+ You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).
+ 3.4. Notices
+ You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.
+ 3.5. Application of Additional Terms
+ You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.
+ 4. Inability to Comply Due to Statute or Regulation
+ If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.
+ 5. Termination
+ 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.
+ 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.
+ 6. Disclaimer of Warranty
+ Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.
+ 7. Limitation of Liability
+ Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
+ 8. Litigation
+ Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims.
+ 9. Miscellaneous
+ This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.
+ 10. Versions of the License
+ 10.1. New Versions
+ Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.
+ 10.2. Effect of New Versions
+ You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.
+ 10.3. Modified Versions
+ If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+ If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.
+ Exhibit A - Source Code Form License Notice
+
+ 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/ .
+
+ If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
+ You may add additional accurate notices of copyright ownership.
+ Exhibit B - “Incompatible With Secondary Licenses” Notice
+
+ This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.
+
+
+
+
+
+
+
+
+
+ com.android.support : animated-vector-drawable
+ org.jetbrains : annotations
+ com.android.support : appcompat-v7
+ com.android.support : cardview-v7
+ android.arch.lifecycle : common
+ android.arch.core : common
+ com.android.support : customtabs
+ com.android.support : design
+ android.arch.lifecycle : extensions
+ org.jetbrains.kotlin : kotlin-stdlib-jre7
+ org.jetbrains.kotlin : kotlin-stdlib
+ com.android.support : recyclerview-v7
+ android.arch.core : runtime
+ android.arch.lifecycle : runtime
+ com.android.support : support-annotations
+ com.android.support : support-compat
+ com.android.support : support-core-ui
+ com.android.support : support-core-utils
+ com.android.support : support-fragment
+ com.android.support : support-media-compat
+ com.android.support : support-v4
+ com.android.support :support-vector-drawable
+ com.android.support :transition
+
+
+ 1. Definitions .
+ "License" shall mean the terms and conditions for use, reproduction, and
+ distribution as defined by Sections 1 through 9 of this document.
+ "Licensor" shall mean the copyright owner or entity authorized by the
+ copyright owner that is granting the License.
+ "Legal Entity" shall mean the union of the acting entity and all other
+ entities that control, are controlled by, or are under common control with
+ that entity. For the purposes of this definition, "control" means (i) the
+ power, direct or indirect, to cause the direction or management of such
+ entity, whether by contract or otherwise, or (ii) ownership of fifty
+ percent (50%) or more of the outstanding shares, or (iii) beneficial
+ ownership of such entity.
+ "You" (or "Your") shall mean an individual or Legal Entity exercising
+ permissions granted by this License.
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation source,
+ and configuration files.
+ "Object" form shall mean any form resulting from mechanical transformation
+ or translation of a Source form, including but not limited to compiled
+ object code, generated documentation, and conversions to other media types.
+ "Work" shall mean the work of authorship, whether in Source or Object form,
+ made available under the License, as indicated by a copyright notice that
+ is included in or attached to the work (an example is provided in the
+ Appendix below).
+ "Derivative Works" shall mean any work, whether in Source or Object form,
+ that is based on (or derived from) the Work and for which the editorial
+ revisions, annotations, elaborations, or other modifications represent, as
+ a whole, an original work of authorship. For the purposes of this License,
+ Derivative Works shall not include works that remain separable from, or
+ merely link (or bind by name) to the interfaces of, the Work and Derivative
+ Works thereof.
+ "Contribution" shall mean any work of authorship, including the original
+ version of the Work and any modifications or additions to that Work or
+ Derivative Works thereof, that is intentionally submitted to Licensor for
+ inclusion in the Work by the copyright owner or by an individual or Legal
+ Entity authorized to submit on behalf of the copyright owner. For the
+ purposes of this definition, "submitted" means any form of electronic,
+ verbal, or written communication sent to the Licensor or its
+ representatives, including but not limited to communication on electronic
+ mailing lists, source code control systems, and issue tracking systems that
+ are managed by, or on behalf of, the Licensor for the purpose of discussing
+ and improving the Work, but excluding communication that is conspicuously
+ marked or otherwise designated in writing by the copyright owner as "Not a
+ Contribution."
+ "Contributor" shall mean Licensor and any individual or Legal Entity on
+ behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+ 2. Grant of Copyright License . Subject to the
+ terms and conditions of this License, each Contributor hereby grants to You
+ a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of, publicly
+ display, publicly perform, sublicense, and distribute the Work and such
+ Derivative Works in Source or Object form.
+ 3. Grant of Patent License . Subject to the terms
+ and conditions of this License, each Contributor hereby grants to You a
+ perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made, use,
+ offer to sell, sell, import, and otherwise transfer the Work, where such
+ license applies only to those patent claims licensable by such Contributor
+ that are necessarily infringed by their Contribution(s) alone or by
+ combination of their Contribution(s) with the Work to which such
+ Contribution(s) was submitted. If You institute patent litigation against
+ any entity (including a cross-claim or counterclaim in a lawsuit) alleging
+ that the Work or a Contribution incorporated within the Work constitutes
+ direct or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate as of the
+ date such litigation is filed.
+ 4. Redistribution . You may reproduce and
+ distribute copies of the Work or Derivative Works thereof in any medium,
+ with or without modifications, and in Source or Object form, provided that
+ You meet the following conditions:
+
+ You must give any other recipients of the Work or Derivative Works a
+ copy of this License; and
+
+ You must cause any modified files to carry prominent notices stating
+ that You changed the files; and
+
+ You must retain, in the Source form of any Derivative Works that You
+ distribute, all copyright, patent, trademark, and attribution notices from
+ the Source form of the Work, excluding those notices that do not pertain to
+ any part of the Derivative Works; and
+
+ If the Work includes a "NOTICE" text file as part of its distribution,
+ then any Derivative Works that You distribute must include a readable copy
+ of the attribution notices contained within such NOTICE file, excluding
+ those notices that do not pertain to any part of the Derivative Works, in
+ at least one of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or documentation,
+ if provided along with the Derivative Works; or, within a display generated
+ by the Derivative Works, if and wherever such third-party notices normally
+ appear. The contents of the NOTICE file are for informational purposes only
+ and do not modify the License. You may add Your own attribution notices
+ within Derivative Works that You distribute, alongside or as an addendum to
+ the NOTICE text from the Work, provided that such additional attribution
+ notices cannot be construed as modifying the License.
+
+
+ You may add Your own copyright statement to Your modifications and may
+ provide additional or different license terms and conditions for use,
+ reproduction, or distribution of Your modifications, or for any such
+ Derivative Works as a whole, provided Your use, reproduction, and
+ distribution of the Work otherwise complies with the conditions stated in
+ this License.
+
+
+
+
+ 5. Submission of Contributions . Unless You
+ explicitly state otherwise, any Contribution intentionally submitted for
+ inclusion in the Work by You to the Licensor shall be under the terms and
+ conditions of this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify the
+ terms of any separate license agreement you may have executed with Licensor
+ regarding such Contributions.
+ 6. Trademarks . This License does not grant
+ permission to use the trade names, trademarks, service marks, or product
+ names of the Licensor, except as required for reasonable and customary use
+ in describing the origin of the Work and reproducing the content of the
+ NOTICE file.
+ 7. Disclaimer of Warranty . Unless required by
+ applicable law or agreed to in writing, Licensor provides the Work (and
+ each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
+ without limitation, any warranties or conditions of TITLE,
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You
+ are solely responsible for determining the appropriateness of using or
+ redistributing the Work and assume any risks associated with Your exercise
+ of permissions under this License.
+ 8. Limitation of Liability . In no event and
+ under no legal theory, whether in tort (including negligence), contract, or
+ otherwise, unless required by applicable law (such as deliberate and
+ grossly negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a result
+ of this License or out of the use or inability to use the Work (including
+ but not limited to damages for loss of goodwill, work stoppage, computer
+ failure or malfunction, or any and all other commercial damages or losses),
+ even if such Contributor has been advised of the possibility of such
+ damages.
+ 9. Accepting Warranty or Additional Liability .
+ While redistributing the Work or Derivative Works thereof, You may choose
+ to offer, and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this License.
+ However, in accepting such obligations, You may act only on Your own behalf
+ and on Your sole responsibility, not on behalf of any other Contributor,
+ and only if You agree to indemnify, defend, and hold each Contributor
+ harmless for any liability incurred by, or claims asserted against, such
+ Contributor by reason of your accepting any such warranty or additional
+ liability.
+ END OF TERMS AND CONDITIONS
+
+
+
+
+
+
+
+
+ findbugs annotations (com.google.code.findbugs:annotations)
+
+
+
+
+Version 2.1, February 1999
+
+
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+
+
+
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+
+
+
+
+
+0.
+This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+
+1.
+You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+
+2.
+You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+
+
+ a)
+ The modified work must itself be a software library.
+ b)
+ You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c)
+ You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d)
+ If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Library, and can be
+reasonably considered independent and separate works in themselves, then
+this License, and its terms, do not apply to those sections when you
+distribute them as separate works. But when you distribute the same
+sections as part of a whole which is a work based on the Library, the
+distribution of the whole must be on the terms of this License, whose
+permissions for other licensees extend to the entire whole, and thus to
+each and every part regardless of who wrote it.
+
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works
+based on the Library.
+
+
+In addition, mere aggregation of another work not based on the Library with
+the Library (or with a work based on the Library) on a volume of a storage
+or distribution medium does not bring the other work under the scope of
+this License.
+
+
+3.
+You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+
+4.
+You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+5.
+A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+
+6.
+As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+
+
+ a) Accompany the work with the complete
+ corresponding machine-readable source code for the Library
+ including whatever changes were used in the work (which must be
+ distributed under Sections 1 and 2 above); and, if the work is an
+ executable linked with the Library, with the complete
+ machine-readable "work that uses the Library", as object code
+ and/or source code, so that the user can modify the Library and
+ then relink to produce a modified executable containing the
+ modified Library. (It is understood that the user who changes the
+ contents of definitions files in the Library will not necessarily
+ be able to recompile the application to use the modified
+ definitions.)
+
+ b) Use a suitable shared library mechanism
+ for linking with the Library. A suitable mechanism is one that
+ (1) uses at run time a copy of the library already present on the
+ user's computer system, rather than copying library functions into
+ the executable, and (2) will operate properly with a modified
+ version of the library, if the user installs one, as long as the
+ modified version is interface-compatible with the version that the
+ work was made with.
+
+ c) Accompany the work with a written offer,
+ valid for at least three years, to give the same user the
+ materials specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by
+ offering access to copy from a designated place, offer equivalent
+ access to copy the above specified materials from the same
+ place.
+
+ e) Verify that the user has already received
+ a copy of these materials or that you have already sent this user
+ a copy.
+
+
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+
+7. You may place library facilities that are a work
+based on the Library side-by-side in a single library together with
+other library facilities not covered by this License, and distribute
+such a combined library, provided that the separate distribution of
+the work based on the Library and of the other library facilities is
+otherwise permitted, and provided that you do these two things:
+
+
+
+ a) Accompany the combined library with a copy
+ of the same work based on the Library, uncombined with any other
+ library facilities. This must be distributed under the terms of
+ the Sections above.
+
+ b) Give prominent notice with the combined
+ library of the fact that part of it is a work based on the
+ Library, and explaining where to find the accompanying uncombined
+ form of the same work.
+
+
+
+8. You may not copy, modify, sublicense, link with,
+or distribute the Library except as expressly provided under this
+License. Any attempt otherwise to copy, modify, sublicense, link
+with, or distribute the Library is void, and will automatically
+terminate your rights under this License. However, parties who have
+received copies, or rights, from you under this License will not have
+their licenses terminated so long as such parties remain in full
+compliance.
+
+
+9.
+You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+
+10.
+Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+
+11.
+If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+12.
+If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+
+13.
+The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+
+14.
+If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+
+NO WARRANTY
+
+
+15.
+BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+
+16.
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+
+END OF TERMS AND CONDITIONS
+
+
+
+
+
+
+
+ Copyright (c) 2012-2017 adjust GmbH,
+ http://www.adjust.com
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/raw/rights.html b/mobile/android/focus-android/app/src/main/res/raw/rights.html
new file mode 100644
index 0000000000..c34c1c5d11
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/raw/rights.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+ %your-rights-content1%
+ %your-rights-content2%
+ %your-rights-content3%
+ %your-rights-content4%
+ %your-rights-content5%
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/transition/firstrun_exit.xml b/mobile/android/focus-android/app/src/main/res/transition/firstrun_exit.xml
new file mode 100644
index 0000000000..535f92a8ad
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/transition/firstrun_exit.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/values-ace/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ace/strings.xml
new file mode 100644
index 0000000000..389880cc8d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ace/strings.xml
@@ -0,0 +1,784 @@
+
+
+
+
+
+
+
+
+ Bateue
+
+ GÖT
+
+ Keubah
+
+
+ Mita atawa pasoe alamat
+
+ Seumeulop rahsia otomatis.\nLop. Sampôh. Ulang.
+
+
+ Riwayat seumeulop droeneuh ka geusampôh.
+ Riwayat peuhah wèb geusampôh
+
+
+ Riwayat peuhah wèb tab ka geusampôh
+
+
+ Mita keu %1$s
+
+
+ Bulueng…
+
+
+ Bri Thèe Isu Situs
+
+
+ Peuhah lam %1$s
+
+
+ Peuhah lam…
+
+
+ Tamah lam layeue Keue
+
+
+ Tamah keu röt koh
+
+ Böh nibak röt koh
+
+
+ Atô
+ Bhaih
+ Beunantu
+ Hak Droeneuh
+
+
+ Peulacak jitham
+
+
+ Neupeulen nyoe jeuet geupubeutôi masalah situs
+
+
+ Theun Asoe
+
+ Peulen keu pubeutôi situs
+
+
+ Jikuasa lé %1$s
+
+
+ Bulueng röt
+
+
+ Sampôh riwayat pumeuhah
+
+
+ Peuhah
+
+
+ Sampôh ngön Peuhah
+
+
+ Sampôh
+
+
+ Sampôh riwayat pumeuhah
+
+
+
+ Sampôh ngön peuhah
+
+
+ Sampôh ngön peuhah %1$s
+
+
+
+ Mita lam Focus
+
+ Mita lam Klar
+
+ Mita lam Focus Beta
+
+ Mita lam Focus Nightly
+
+
+ %1$s peuduek droeneuh lam keumudoe.
+Ngui sibagoe browser rahsia:
+
+ Mita ngön lop lam aplikasi
+ Tham ureueng seutöt (atawa pubarô seuneuatô keu peuidin ureueng seutöt)
+ Lhi keu sampôh cookies lagèe riwayat mita ngön seumeulop
+
+
+%1$s geupeugöt lé Mozilla. Misi kamoe nakeuh keu meusatoh internet nyang sihat ngön teuhah.
+Learn more
]]>
+
+
+ Privasi & Keuamanan
+
+
+ Seumeutöt, kuki, peuniléh data
+
+
+ Ato teutap, peuleungkap keudroe
+
+
+
+
+ Bhaih %1$s, beunantu
+
+
+ Tamah Lindông Seumeutöt
+
+
+ Asoe Web
+
+
+ Tuka App
+
+
+ Umom
+
+
+ Browser baku, bahsa
+
+
+ Peusapat Data & Ngui
+
+ Mita
+
+
+ Peuleumah saran mita
+
+
+ %1$s meung geupeuét peue nyang neukeutik lam bateueng alamat keu meusén mita droeneuh
+
+
+ Teutap
+
+
+ Meusén mita
+
+
+ Udép
+
+
+ Maté
+
+
+ Peuleungkap URL keudroe
+
+
+ Keu situs hayeue
+
+
+ Peuudép keu na %s peuleungkap otomatis keu 450 boh URL nyang meusyeuhu lam bateueng alamat.
+
+
+ Keu situs nyang neutamah
+
+
+ Peuudép keu na %s peuleungkap otomatis alamat URL geunalak droeneuh.
+
+
+ Atô situs
+
+
+ Atô situs
+
+
+ + Tamah URL kustom
+
+
+ Dapeuta auto leungkap droeneuh:
+
+
+ Tamah URL
+
+
+ Tamah URL kustom
+
+
+ Tamah URL kustom
+
+
+ Tamah peunawôt keu peuleungkap keudroe
+
+
+ Kuki ngön Data Situs
+
+
+ Peuniléh Data
+
+
+ Sampôh URL kustom
+
+
+ Meurunoë lom
+
+
+ Tamah ngön atô URL peuleungkap keudroe kustom
+
+
+ URL keu geutamah
+
+
+ Tipèk atawa pasoe URL
+
+
+ Miseue: mozilla.org
+
+
+ Miseue: miseue.com
+
+
+ URL kustom barô ka geutamah.
+
+
+ Sampôh
+
+
+ Sampôh
+
+
+ Paréksa dua go URL nyang neupasoe.
+
+ Bahsa
+
+ Bawaan sistem
+
+ Privasi
+ Tham peulacak iklan
+
+ Ladôm iklan jiseutöt seunaweue situs, bahpih meunyo hana neuteugön iklan nyan
+ Tham peulacak analitik
+ Geungui keu peusaho, analisis ngön seumipat buet lagèe klik ngon skrol
+ Tham peulacak sosial
+
+ Geupeukeumat bak situs keu jilacak seunaweueneuh ngön jipeuleumah fungsi lagèe teuneuthèp bulueng
+ Tham peulacak asoe nyang laén
+ Meunyo neupeuudép akébatjih ladôm mieng mungkén akan jimeulagèe
+ Tham kuki
+
+
+ Han
+ Tham kuki peulacak pihak keulhèe mantöng
+ Tham kuki pihak keulhèe mantöng
+ Tham kuki lintas-situs
+ Nyo
+
+
+ Ngui sidék jaroe keu peuhah aplikasi
+
+ Ngui sidék jaroe keu peuhah meunyo neutamah shortcut atawa watèe situs wèb ka teuhah bak %s.
+
+
+ Bèk peuleumah
+
+
+ Som mieng wèb watèe gantoe aplikasi ngon tham cok drop layeue.
+
+ Seuneuaman
+
+ Hasé buet
+ Tham haraih web
+
+
+ Jeuet gadöh ikon atawa gamba
+
+ Blokir JavaScript
+
+
+ Mieng jeuet jipeuhah leubèh bagah, tapi jeuet cit jimubuet lagèe nyang kön geuharap
+
+
+ Peujeuet %1$s browser teutap
+
+ Mozilla
+
+ Peuét data teungui
+
+
+ Meurunoë lom
+
+
+ Mozilla geuôseuha keu geupeusaho nyang geupeureulèe mantöng keu geuseudiya dan geupeujroh %1$s keu bandum ureueng.
+
+
+ Reusam Privasi
+
+
+ Keutrangan lisènsi
+
+
+ Pustaka nyang meungui
+
+
+ %s | Pustaka OSS
+
+
+ Bhaih %1$s
+
+
+ Meusén mita teupasang
+
+
+ Piléh meusén mita
+
+
+ Peuriwang meusén mita teutap
+
+
+ + Tamah meusén mita laén
+ Sampôh meusén mita
+ Sampôh
+
+
+ Tamah meusén mita nyang laén
+
+
+ Piléh meusén nyang neugalak:
+
+
+ Tamah meusén mita
+
+ Nan meusén mita
+ String mita keu geungui
+ Keubah
+
+
+ Miseue: miseue.com/mita/?q=%s
+
+ Meusén mita nyang barô geutamah.
+
+ Peutamong nan meusén mita
+
+ Meusén mita nyang teupasang ka geungui nan nyan.
+
+ Peutamong string mita
+
+ Neuparéksa meunyo string meunita ka pah ngön beuntuk Miseue
+
+
+ Peugléh teunamong
+
+
+ Tôp
+
+
+ Sampôh riwayat pumeuhah
+
+
+ Tab teuhah: %1$s
+
+
+ Seunambat aman
+
+
+ Teungöh jipeuhah
+
+
+ Situh wèb ka teuhah
+
+
+ Peuniléh laén
+
+
+ Tombol peuniléh laén
+
+
+ Jak u keue
+
+
+ Peuhah ulang situs wèb
+
+
+ Gisa u likôt
+
+
+ Bèk lé peuhah situihweb
+
+
+ Gisa u app sat nyoe
+
+
+ Jumeulah peulacak teutham
+
+
+ Blokir peulacak
+
+ Hak Droeneuh
+
+ Peuhah link lam aplikasi laén
+
+
+ Droeneuh jeuet neutinggai %1$s keu neupeuhah peunawôt nyoe bak %2$s.
+
+ Mita saboh app nyang jeuet peuhah link
+
+
+ Hana meu sabôh app lam peukakah droeneuh nyang jeuet peuhah link nyoe. Droeneu jeuet neutinggai %1$s keu jak mita %2$s keu app nyang jeuet.
+
+
+ Teubiet Pumeuhah Rahsia?
+
+
+ %1$s seuleusoe
+
+
+ Peuhah
+
+
+ Geutamah u röt koh!
+
+ Server hana geuteumeung
+
+
+ Tôp
+
+
+
+ Neupiyôh bak %1$s
+
+
+ Bagah. Rahsia. Hana karu.
+
+
+ Puphôn
+
+
+
+ %1$s kön lagèe browser laén
+
+
+ Peujeuet sibagoe browser teutap
+
+
+ Lingkeue
+
+
+ Peuköng privasi droeneuh
+
+
+ Hasé mita droeneuh, kri droeneuh
+
+
+ Peujeuet privasi jeuet keu reusam
+
+ Göt, lôn muphôm!
+ Lingkeue
+ Lom
+
+
+ -
+
+
+ Tamah
+
+ NYO
+
+
+ Bateuë
+
+
+ HAN
+
+
+ Sesi pumeuhah rahsia
+
+
+ Sampôh riwayat pumeuhah
+
+
+ Cok keubah Firefox
+
+
+ Nan ureung ngui
+ Lageuem tamöng
+ Peugléh
+
+
+
+ Seunambat Aman
+ Seunambat Hana Aman
+
+ Geupeusahèh lé: %1$s
+
+
+ Keuamanan Situh
+ URL ka na
+
+
+ Mita lam laman
+
+
+ Mita lam laman
+
+
+ %1$d/%2$d
+
+ %1$d nibak %2$d
+
+
+ Kalön hasé u keue
+
+ Kalön hasé u likôt
+
+ Sampôh nyang meuteumèe lam laman
+
+
+ Lakèe situs desktop
+
+
+ Situs desktop
+
+
+ URL teusalén
+
+
+ Alat peukeumang
+
+
+ Peuhah peunawôt bak aplikasi
+
+
+ Leubèh le
+
+
+ Idin situs
+
+
+ Peukureueng Spanduk Kuki
+
+
+ Udép
+
+
+ Maté
+
+
+ Peukureueng Spanduk Kuki
+
+ -->
+ Peukureueng Spanduk Kuki
+
+
+ HU keu situh nyoe
+
+
+ LEN keu situh nyoe
+
+
+ Peukureueng Spanduk Kuki
+
+
+ LEN keu situh nyoe
+
+
+ HU keu situh nyoe
+
+
+ Bateue
+
+
+ Lakèe dukôngan
+
+
+ atô
+
+
+ Puta Keudroe
+
+
+ Keu peuidin:
+
+
+ 1. Jak u Atô Android
+
+
+ Idin]]>
+
+
+ Atô
+
+
+ %1$s jeuet ON]]>
+
+
+ Kamèra
+
+
+ Mikrofon
+
+
+ Neuduek
+
+
+ Bri thèe
+
+
+ Lakèe idin
+
+
+ Geutham
+
+
+ Geupeuidin
+
+
+ Geutham lé Android
+
+
+ Peuidin su ngön video
+
+
+ Geupeusaran
+
+
+ Tham audio ngön video
+
+
+ Beuet
+
+
+ Sampôh
+
+
+ Aktif
+
+
+ Seuleusoe
+
+
+ Peuhah
+
+
+ Peuhah peunawôt bak Sèsi Barô
+
+
+ H‘an
+
+
+ Nyo
+
+
+ Tôp
+
+
+
+
+ Gantoe
+
+
+ Keucuwali
+
+ Sampôh
+
+ Sampôh bandum situs
+
+
+ Tham Kuki
+
+
+ Tôp Tab
+
+
+
+
+ Tamah Lindông Seumeutöt
+
+
+
+ Layeue
+
+ Peungeuh
+
+ Seupôt
+
+ Geuatô lé Himat Batré
+
+ Seutöt layeue peukakaih
+
+
+ Tôp
+
+
+
+
+ Tôp
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-af/strings.xml b/mobile/android/focus-android/app/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000000..3fe8224ae2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-af/strings.xml
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+ Kanselleer
+
+ OK
+
+ Stoor
+
+
+ Soek, of tik adres in
+
+ Outomatiese privaat-lees modus.\nLees. Vergeet. Herhaal.
+
+
+ Jou leesrekord is uitgevee.
+
+
+ Soek vir %1$s
+
+
+ Deel…
+
+
+ Rapporteer \'n probleem met die webwerf
+
+
+ Maak oop in %1$s
+
+
+ Maak oop in…
+
+
+ Voeg by tuisskerm
+
+ Instellings
+ Inligting hieroor
+ Hulp
+ Jou regte
+
+
+ Moontlik gemaak deur %1$s
+
+
+ Deel via
+
+
+ Skrap leesgeskiedenis
+
+
+ Maak oop
+
+
+ Skrap en maak oop
+
+
+ Skrap
+
+
+ Skrap leesgeskiedenis
+
+
+
+ Privaatheid en sekuriteit
+
+
+ Naspoor-koekies, data keuses
+
+
+ Stel verstekwaardes, outovoltooi
+
+
+
+
+ Meer oor %1$s, hulp
+
+
+ Webinhoud
+
+
+ Wissel tussen toepassings
+
+
+ Algemeen
+
+
+ Data versameling & gebruik
+
+ Soek
+
+
+ Verstek
+
+
+ Aan
+
+
+ Af
+
+
+ URL Outovoltooi
+
+
+ Datakeuses
+
+
+ Meer inligting
+
+
+ Plak of tik URL
+
+
+ Voorbeeld: voorbeeld.com
+
+
+ Verwyder
+
+
+ Verwyder
+
+ Taal
+
+ Privaatheid
+ Sommige advertensies hou rekord van jou webwerfbesoeke, selfs al kliek jy nie op hulle nie
+ Blokkeer sosiale speurders
+
+ Mozilla
+
+
+ Meer inligting
+
+
+ Privaatheidkennisgewing
+
+ Verwyder soekenjins
+ Verwyder
+
+
+ Voeg soekenjin by
+
+ Stoor
+
+ Nuwe soekenjin bygevoeg.
+
+
+ Beveiligde verbinding
+
+
+ Besig om te laai
+
+
+ Webwerf gelaai
+
+
+ Meer keuses
+
+
+ Navigeer vorentoe
+
+
+ Herlaai webwerf
+
+
+ Navigeer terug
+
+
+ Gaan terug na vorige toep
+
+ Jou regte
+
+
+ Maak oop
+
+
+
+ Bediener nie gevind nie
+
+
+
+
+
+
+ Jou soek, jou manier
+
+ Reg so!
+ Volgende
+
+
+ -
+
+
+ Voeg by
+
+
+ Kanselleer
+
+
+ Skrap blaaigeskiedenis
+
+
+ Laai Firefox af
+
+
+ Gebruikersnaam
+ Wagwoord
+ Vee uit
+
+
+
+ Beveiligde verbinding
+ Onbeveiligde verbinding
+
+ Geverifieer deur: %1$s
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-am/strings.xml b/mobile/android/focus-android/app/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000000..4fb899dc51
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-am/strings.xml
@@ -0,0 +1,1096 @@
+
+
+
+
+
+
+
+
+ ተወው
+
+ እሺ
+
+ አስቀምጥ
+
+
+ ፍለጋ ወይም አድራሻ ያስገቡ
+
+ አውቶማቲክ የግል አሰሳ\nያስሱ. ደምስስ. ድገም.
+
+
+ የአሰሳ ታሪክዎ ተደምስሷል
+
+ የአሰሳ ታሪክ ጸድቷል
+
+
+ የትሩ የአሰሳ ታሪክ ተደምስሷል።
+
+
+ ፈልግ ስለ %1$s
+
+
+ ያጋሩ…\n\n
+
+
+ የድረ-ገጹን ችግር አመልክት
+
+
+ በ %1$s ክፈት
+
+
+ ክፈት በ…
+
+
+ ወደ መነሻ ማያ ገጽ አክል
+
+
+ ወደ አቋራጭ ጨምር
+
+ ከአቋራጭ ላይ አስወድግ
+
+
+ ቅንብሮች
+ ስለ
+ እገዛ
+ የእርስዎ መብቶች
+
+
+ ተከታታዮች ታግደዋል
+
+
+ ይህን ማጥፋት አንዳንድ የድረ-ገፅ ችግሮችን ሊፈታ ይችላል
+
+
+ የይዘት እገዳ
+
+
+ አንዳንድ ድረ-ገፆችን ለመጠገን አጥፋ
+
+
+ በ%1$s የተጎላበተ
+
+
+ ያጋሩ በ
+
+ የአሰሳ ታሪክን ይጥፋ?
+ የአሰሳ ታሪክዎን ደህንነቱ በተጠበቀ መልኩ ለማጥፋት ይህን ማሳወቂያ መታ ያድርጉ ወይም ያጽዱ።
+
+
+ የአሰሳ ታሪክዎን ደህንነቱ በተጠበቀ መልኩ ለማጥፋት ይህን ማሳወቂያ መታ ያድርጉ ወይም ያንሸራትቱ።
+
+ የአሰሳ ታሪክን አጥፋ
+
+
+ ክፈት
+
+
+ አጠፉ እና ክፈት
+
+
+ አጠፉ
+
+
+ የአሰሳ ታሪክን አጥፋ
+
+
+
+ አጥፋ እና ከፈት
+
+
+ አጥፋ እና ክፈት %1$s
+
+
+ Focus ላይ ፈልግ
+
+ Klar ላይ ፈልግ
+
+ Focus Beta ላይ ፈልግ
+
+ Focus Nightly ላይ ፈልግ
+
+
+ %1$s እርስዎን ውሣኔ ሰጪ ያደርግዎታል።
+እንደ የግል አሳሽ ይጠቀሙበት፡-
+
+ በመተግበሪያው ውስጥ ይፈልጉ እና ያስሱ
+ መከታተያዎችን ያግዱ (ወይም መከታተያዎችን ለመፍቀድ ቅንብሮችን ያዘምኑ)
+ ኩኪዎችን እንዲሁም የፍለጋ እና የአሰሳ ታሪክን ለማጥፋት ይደምስሱ
+
+
+%1$s የሚመረተው በሞዚላ ነው። የእኛ ተልዕኮ ጤናማ እና ክፍት በይነመረብን ማዳበር ነው።
+የበለጠ ለመረዳት
]]>
+
+
+ ግላዊነት እና ደህንነት
+
+
+ ክትትል፣ ኩኪዎች፣ የውሂብ ምርጫዎች
+
+
+ ነባሪ አዘጋጅ፣ ራስ-አጠናቅቅ
+
+
+
+
+ ስለ %1$s፣ እገዛ
+
+
+ የተሻሻለ የመከታተያ ጥበቃ
+
+
+ የድር ይዘት
+
+
+ መተግበሪያዎችን በመቀያየር ላይ
+
+
+ አጠቃላይ
+
+
+ ነባሪ አሳሽ፣ ቋንቋ
+
+
+ የውሂብ ስብሰባ እና አጠቃቀም
+
+ ፍለጋ
+
+
+ የፍለጋ ጥቆማዎችን ያግኙ
+
+ %1$s በአድራሻ አሞሌው ላይ የሚተይቡትን ወደ የፍለጋ ፍርግምዎ ይልካል
+
+
+ ነባሪ
+
+
+ የፍለጋ ፍርግም
+
+
+ በርቷል
+
+
+ ጠፍቷል
+
+
+ URL ራስ-አጠናቅቅ
+
+
+ ለዋና ዋና ድረ-ገፆች
+
+
+ በአድራሻ አሞሌው ውስጥ ከ450 በላይ ታዋቂ ዩአርኤሎችን %s በራስ ሰር እንዲያጠናቅቅ ያንቁ።
+
+
+ ለሚያክሏቸው ድረ-ገፆች
+
+
+ የሚወዷቸውን ዩአርኤሎች %s በራስ ሰር እንዲያጠናቅቅ ያንቁ።
+
+
+ ድረ-ገፆችን ያስተዳድሩ
+
+
+ ድረ-ገፆችን ያስተዳድሩ
+
+
+ + ብጁ አክል URL
+
+
+ የእርስዎ ራስ-አጠናቅቅ ዝርዝር፡-
+
+
+ ዩአርኤል አክል
+
+
+ ብጁ አክል URL
+
+
+ ብጁ ዩአርኤል አክል
+
+
+ ወደ ራስ-አጠናቅቅ አገናኝ አክል
+
+
+ ኩኪዎች እና የድረ-ገፅ ውሂብ
+
+
+ የውሂብ ምርጫዎች
+
+
+ ብጁ አስወግድ URLs
+
+
+ ተጨማሪ እወቅ
+
+
+ አክል ወይም አደራጅ ብጁ ራስ-አጠናቅቅ URLs
+
+
+ URL መጨመር
+
+
+ ለጥፍ ወይም ግባ URL
+
+
+ ምሳሌ: mozilla.org
+
+
+ ምሳሌ : ምሳሌ.com
+
+
+ አዲስ ብጁ URL ታክሏል
+
+
+ አስወግድ
+
+
+ አስወግድ
+
+
+ በድጋሚ አረጋጡ ያስገቡትን URL
+
+ ቋንቋ
+
+ የስርዓት ነባሪ
+
+ የግል መብት መጠበቅ
+ የማስታወቂያ መከታተያዎችን አግድ
+ "ማስታወቂያዎች ባይጫኑ እንኳን አንዳንድ የማስታወቂያ መከታተያዎች የጣቢያ ጉብኝቶችን ይከታተላሉ "
+ መተንተኛ ተቆጣጣሪዎች ያግዱ
+ እንደ ማሸብለል እና መታ በማድረግ ያሉ እንቅስቃሴዎችን ለመተንተን እና ለመለካት ጥቅም ላይ የዋለ
+ ማህበራዊ ሚዲያ መከታተያዎችን አግድ\n
+ የእርስዎን ጉብኝቶች ለመከታተል እና እንደ የአጋራ አዝራሮች ያሉ ተግባሮችን ለማሳየት በጣቢያዎች ላይ የተካተተ
+ ሌሎች የይዘት መከታተያዎችን ያግዱ\n
+ ማስቻል አንዳንድ ገፆችን ያልታሰበ ፀባይ እንዲፈጽሙ ሊያደርጋቸው ይችላል
+ ኩኪዎችን አግድ
+
+
+ አልፈልግም፣አመሰግናለሁ
+ የሶስተኛ ወገን መከታተያ ኩኪዎችን ብቻ አግድ
+ የሶስተኛ ወገን ኩኪዎችን ብቻ አግድ
+
+ ከድረ-ገፅ ድረ-ገፅ የሚከታተሉ ኩኪዎችን አግድ
+ አዎ እባክዎ
+
+
+ መተግበሪያን ለመክፈት የጣት አሻራ ይጠቀሙ
+
+ አቋራጮችን ካከሉ ወይም አንድ ድረ-ገፅ በ%s ውስጥ ክፍት ከሆነ፤ የጣት አሻራ በመጠቀም ይክፈቱ።
+
+
+ ስውር
+
+ መተግበሪያዎች በሚቀይሩበት ጊዜ ድረ-ገጾችን ደብቅ እና ስክሪንሻት መውሰድን አግድ።
+
+ ደህንነት
+
+ አፈፃፀም
+ የድር ቅርጸ ቁምፊዎችን አግድ
+
+ አዶዎችን ወይም ምስሎችን የጠፉ ሊያስከትል ይችላል\n
+
+ ጃቫ-ስክሪፕት አግድ
+
+ ገፆች በፍጥነት ሊከፍቱ ይችላሉ፣ ግን ደግሞ ያልተጠበቀ ባህሪ ሊኖራቸው ይችላል
+
+
+ አከናውን %1$s ነባሪ አሳሽ
+
+ ሞዚላ
+ የአጠቃቀም ውሂብ ይላኩ
+
+
+ ተጨማሪ ይወቁ
+
+
+ ሞዚላ %1$s ለሁሉም ለማቅረብ እና ለማሻሻል የሚያስፈልገንን ብቻ ለመሰብሰብ ይጥራል
+
+
+ የግላዊነት ማሳወቂያ
+
+
+ የፈቃድ መረጃ
+
+
+ የምንጠቀማቸው ጥቅሎች
+
+
+ %s | ክፍት ምንጭ ሶፍትዌር (OSS) ጥቅሎች
+
+
+ ስለ %1$s
+
+
+ የተጫኑ የፍለጋ ፕሮግራሞች
+
+
+ የፍለጋ ፍርግም ይምረጡ
+
+
+ ነባሪ የፍለጋ ፕሮግራሞችን ወደነበረበት መልስ
+
+
+ + ሌላ የፍለጋ ፕሮግራም አክል
+ የፍለጋ ፕሮግራሞችን አስወግድ
+ አስወግድ
+
+
+ ሌላ የፍለጋ ፍርግም አክል
+
+ የእርስዎን ተመራጭ ፍርግም ይምረጡ፡-
+
+
+ የፍለጋ ፕሮግራም አክል\n
+
+ የመፈለጊያ ማሸን ስም
+ የሚጠቀሙ ሕብረ ቁምፊዎችን ፈልግ
+ አስቀምጥ
+
+
+ ለምሳሌ: ለምሳሌ.com /search/?q=%s
+
+ አዲስ የፍለጋ ፕሮግራም ታክሏል
+
+ የፍለጋውን ስም አስገባ
+ አንድ የተጫነ የፍለጋ ፕሮግራም አስቀድሞ በዚያ ስም እየተጠቀመ ነው\n
+
+ የፍለጋ ህብረ ቁምፊ ያስገቡ
+
+ የፍለጋ ሕብረቁምፊ ና የምሳሌ ቅርጸት እንደ ሚመሳሰሉ አረጋግጥ
+
+
+ ግቤት አጽዳ
+
+
+ አሰናብት
+
+
+ የአሰሳ ታሪክን አጥፋ
+
+
+ ትሮች ተከፍተዋል: %1$s
+
+
+ ደህንነቱ የተጠበቀ ግንኙነት
+
+
+ በመጫን ላይ
+
+
+ ድር ጣቢያ ተጭኗል
+
+
+ ተጨማሪ አማራጮች
+
+
+ ተጨማሪ አማራጮች አዝራር
+
+
+ ወደ ፊት አስስ
+
+
+ ድር ጣቢያ እንደገና ይጫኑ
+
+
+ ወደ ኋላ ዳስስ
+
+
+ ድር ጣቢያ መጫን አቁም
+
+
+ ወደ ቀዳሚው መተግበሪያ ይመለሱ
+
+
+ የታገዱ መከታተያዎች ብዛት
+
+
+ መከታተያዎችን አግድ
+
+ የእርስዎ መብቶች
+
+ አገናኙን በሌላ መተግበሪያ ውስጥ ይክፈቱ
+
+ መሄድ ይችላሉ %1$s ይህንን አገናኝ በ ውስጥ ለመክፈት %2$s
+
+ አገናኙን መክፈት የሚችል መተግበሪያ ያግኙ
+
+ በመሣሪያዎ ላይ ካሉት መተግበሪያዎች ውስጥ አንዳቸውም ይህን አገናኝ መክፈት አይችሉም። የሚችል መተግበሪያ %2$s ለመፈለግ %1$sን ትተው መሄድ ይችላሉ።
+
+ የግል አሰሳውን ውጣ?
+
+
+ %1$s ተጠናቅቋል
+
+
+ ክፈት
+
+
+
+
+
+
+
+
+
+
+ ወደ አቋራጮች ታክሏል!
+
+ አገልጋይ አልተገኘም
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ዝጋ
+
+
+
+ እንኳን ወደ %1$s በደህና መጡ
+
+
+ ፈጣን። የግል። ትኩረትን የሚከፋፍሉ ነገሮች የሌለው።
+
+
+ ይጀምሩ
+
+
+
+ %1$s እንደ ሌሎች አሳሾች አይደለም
+
+
+ ለተጨማሪ ግላዊነት መተግበሪያውን ሲዘጉ ታሪክዎን እናጸዳለን።
+
+
+
+ በሚከፍቱት እያንዳንዱ አገናኝ ውሂብዎን ለመጠበቅ %1$s ነባሪ ያድርጉ።
+
+
+ ነባሪ አሳሽ አድርገህ አስቀምጥ
+
+
+ ዝለል
+
+
+ ግላዊነትዎን ያስፉ
+
+ የግል ፍለጋን ወደ ቀጣዩ ደረጃ ይወስዳል::በጣቢያዎች ውስጥ እርስዎን ሊከታተል የሚችል እና የገፅ ጭነት ጊዜዎችን ማደንዘዝ ማስታወቂያዎችን እና ሌላ ይዘትን አግድ
+
+
+ የእርስዎ ፍለጋ, የእርስዎ መንገድ
+
+ የሆነ የተለየ ነገር በመፈለግ ላይ? በቅንብሮች ውስጥ ሌላ ነባሪ የፍለጋ ፕሮግራም ይምረጡ
+
+
+ ወደ መነሻ ማያ ገጽዎ አቋራጮችን ያክሉ
+
+ ወደ እርስዎ ተወዳጅ ጣቢያዎች ይመለሱ %1$s በፍጥነት:: በቀላሉ ይምረጡ \"ወደ መነሻ ማያ ገጽ አክል\" ከ ዘንድ %1$s ምናሌ::
+
+
+ ግላዊነት የማድረግ ልማድ ያድርጉ
+
+ እንደ %1$s ነባሪ አሳሽዎ አድርገው ያቀናብሩ እና የድር ገጾች ከሌላ መተግበሪያዎች ሲከፍቱ የግል ማሰሻዎችን ጥቅሞች ያግኙ
+
+ እሺ ገባኝ!
+ እለፍ
+ ቀጣይ
+
+
+ -
+
+
+ ጨምር
+
+
+ አዎ
+
+
+ አጥፋ
+
+
+ ይቅር
+
+
+ አቋራጩ የተሻሻለ የክትትል ጥበቃ የሌለው ሆኖ ይከፈታል
+
+
+ የግል የማሰሻ ክፍል
+
+
+ ማሳወቂያዎች የ%1$s ክፍለ ጊዜዎን አንዴ በመንካት እንዲሰርዙ ያስችሉዎታል። መተግበሪያውን መክፈት ወይም በአሳሽዎ ውስጥ ምን እንደሚካሄድ ማየት አያስፈልግዎትም።
+
+
+ የማሰሻ ታሪክን አጥፋ
+
+
+ ፋየርፎክስ አውርድ
+
+
+
+
+
+ ሞዚላ የሕዝብ ፈቃድ እና ሌሎች የክፍት ምንጭ ፈቃዶች ውል መሠረት ነው።]]>
+
+
+ እዚህ ሊገኝ ይችላል።]]>
+
+
+ ፈቃዶች ስር ይገኛል።]]>
+
+
+ በጂኤንዩ አጠቃላይ የህዝብ ፍቃድ v3 ስር እንደ ገለልተኛ ስራዎች ይጠቀማል፤ እና እዚህ ይገኛል።]]>
+
+
+ የተጠቃሚ ስም
+ የይለፍ ቃል
+ አጽዳ
+
+
+
+ ደህንነቱ የተጠበቀ ግንኙነት
+ ደህንነቱ ያልተጠበቀ ግንኙነት
+
+ አረጋጋጭ: %1$s
+
+
+ የድረ-ገፅ ደህንነት
+ ዩአርኤል አስቀድሞ አለ
+
+
+ በገጽ ውስጥ ያግኙ
+
+
+ በገጽ ውስጥ ያግኙ
+
+
+ %1$d/%2$d
+
+ %1$d ከ%2$d
+
+
+ የሚቀጥለውን ውጤት ያግኙ
+
+ ያለፈውን ውጤት ያግኙ
+
+ በገጽ ውስጥ ማግኘትን አሰናብት
+
+
+ የዴስክቶፕ ድረ-ገፅን ይጠይቁ
+
+
+ የዴስክቶፕ ድረ-ገፅ
+
+
+ ዩአርኤል ተቀድቷል
+
+
+ የገንቢ መሳሪያዎች
+
+
+ አገናኞችን በመተግበሪያዎች ውስጥ ይክፈቱ
+
+
+ የላቀ
+
+
+ የድረ-ገፅ ፈቃዶች
+
+
+ የኩኪ ባነር ቅነሳ
+
+
+ በርቷል
+
+
+ ጠፍቷል
+
+
+ የኩኪ ባነር ቅነሳ
+
+
+ በተቻለ መጠን የኩኪ ጥያቄዎችን በራስ-ሰር ውድቅ በማድረግ ያነሱ ባነሮችን ይመልከቱ።
+
+ -->
+ የኩኪ ባነር ቅነሳ
+
+
+ ለዚህ ድረ-ገፅ በርቷል
+
+
+ በአሁኑ ጊዜ የማይደገፍ ድረ-ገፅ
+
+
+ ለዚህ ድረ-ገፅ ጠፍቷል
+
+
+ የኩኪ ባነር ቅነሳ
+
+
+ ለዚህ ድረ-ገፅ ጠፍቷል
+
+
+ ለዚህ ድረ-ገፅ በርቷል
+
+
+ ለ%1$s የኩኪ ባነር ቅነሳ ይብራ?
+
+
+ ለ%1$s የኩኪ ባነር ቅነሳ ይጥፋ?
+
+
+ %1$s የዚህን ጣቢያ ኩኪዎች ያጠራል እና ገጹን ያድሳል። ሁሉንም ኩኪዎች ማጽዳት እርስዎን ሊያስወጣዎት ወይም የግዢ ጋሪዎችን ባዶ ሊያደርግ ይችላል።
+
+
+ %1$s የኩኪ ጥያቄዎችን በራስ ሰር ውድቅ ለማድረግ መሞከር ይችላል።
+
+
+ ይህ ድረ-ገጽ በአሁኑ ጊዜ በኩኪ ሰሌዳ ቅነሳ አይደገፍም። ቡድናችን ይህንን ድረ-ገጽ እንዲገመግም እና ለወደፊቱ ድጋፍ እንዲጨምር መጠየቅ ይፈልጋሉ?
+
+
+ ተወው
+
+
+ ድጋፍ ይጠይቁ
+
+
+ የድረ-ገጽ ድጋፍ ጥያቄ ገብቷል።
+
+
+ የድረ-ገጽ ድጋፍ ጥያቄ ገብቷል።
+
+
+
+ %1$s የሚረብሹ የኩኪ ባነሮችን ለማሰናበት የኩኪ ጥያቄዎችን ውድቅ ለማድረግ ይሞክራል።\n\nየኩኪ ባነር ምርጫዎችን በ%2$s ውስጥ ያስተዳድሩ።
+
+ ቅንብሮች
+
+
+ ራስ-አጫዋች
+
+
+ ለመፍቀድ:-
+
+
+ 1. ወደ አንድሮይድ መቼቶች ይሂዱ
+
+
+ ፍቃዶችን መታ ያድርጉ]]>
+
+
+ ወደ ቅንብሮች ይሂዱ
+
+
+ %1$s ን ወደ አብራ ቀይር]]>
+
+
+ ካሜራ
+
+
+ ድምፅ ማጉያ
+
+
+ ቦታ
+
+
+ ማሳወቂያ
+
+
+ በDRM ቁጥጥር የሚደረግበት ይዘት
+
+
+ ለመፍቀድ ጠይቅ
+
+
+ ታግዷል
+
+
+ ተፈቅዷል
+
+
+ በአንድሮይድ ታግዷል
+
+
+ ድምፅ እና ተንቀሳቃሽ ምሥል ፍቀድ
+
+
+ ድምፅን ብቻ አግድ
+
+
+ የሚመከር
+
+
+ ድምፅ እና ተንቀሳቃሽ ምሥል አግድ
+
+
+ ጥናቶች
+
+
+ ፋየርፎክስ ከጊዜ ወደ ጊዜ ጥናቶችን ሊጭን እና ሊያሄድ ይችላል።
+
+
+ ተጨማሪ ይወቁ
+
+
+ ለውጦችን ለመተግበር መተግበርያው ያቆማል
+
+
+ አስወግድ
+
+
+ ንቁ
+
+
+ የተጠናቀቀ
+
+
+ በUSB/Wi-Fi በኩል የርቀት ማረም
+
+
+ ይክፈቱ
+
+
+ የጣት አሻራዎን በመጠቀም ያረጋግጡ
+
+
+ የአሁኑን የመተግበሪያ ክፍለ ጊዜዎን ለመቀጠል የጣት አሻራዎን መጠቀም ይችላሉ።
+
+
+ አገናኝ በአዲስ ክፍለ ጊዜ ክፈት
+
+
+ የጣት አሻራ አዶ
+
+
+ የማይታወቅ የጣት አሻራ። እንደገና ይሞክሩ።
+
+
+ ጣትዎ በጣም በፍጥነት ተንቀሳቅሷል። እንደገና ይሞክሩ።
+
+
+ የፍለጋ ጥቆማዎችን አሳይ?
+
+
+ ጥቆማዎችን ለማግኘት %1$s በአድራሻ አሞሌው ላይ የሚተይቡትን ወደ ፍለጋ ፍርግም መላክ አለበት።
+
+
+ ይቅር
+
+
+ አዎ
+
+
+ አንዳንድ የፍለጋ ፍርግሞች ጥቆማዎችን ማሳየት አይችሉም።
+
+
+ አሰናብት
+
+
+
+
+ ድረ-ገፁ ያልተጠበቀ ባህሪ አለው?\n
+ የክትትል ጥበቃን በማጥፋት ይሞክሩ
+
+
+ ወደ መነሻ ገፅ አክል]]>
+
+
+ እያንዳንዱን አገናኝ በ %1$s ክፈት\n
+ %1$sን ነባሪ አሳሽ አድርግ
+
+
+ በብዛት ለሚጠቀሙባቸው ድረ-ገፆች ዩአርኤሎችን በራስ ሰር ያጠናቅቁ\n
+ በአድራሻ አሞሌው ውስጥ ማንኛውንም ዩአርኤል በረጅሙ ይጫኑ
+
+
+ በአዲስ ትር ውስጥ አገናኝ ክፈት\n
+ በገጽ ላይ ያለውን ማንኛውንም አገናኝ በረጅሙ ይጫኑ
+
+
+ በመነሻ ማያ ገጹ ላይ ጠቃሚ ምክሮችን አጥፋ
+
+
+ አዲስ ትር ተከፍቷል
+
+
+ ቀይር
+
+
+ ወደ ሙሉ ማያ ገጽ ሁነታ በመግባት ላይ
+
+
+ ወደ አገናኝ አዲስ ትር ወዲያውኑ ቀይር
+
+
+ አደገኛ ሊሆኑ የሚችሉ እና አታላይ ድረ-ገፆችን አግድ
+
+ ሪፖርት የተደረጉ የማታለል እና የጥቃት ድረ-ገፆችን፣ የማልዌር ድረ-ገፆችን እና ያልተፈለጉ የሶፍትዌር ድረ-ገፆችን አግድ።
+
+ HTTPS-ብቻ ሁነታ
+
+ ለበለጠ ደህንነት የ HTTPS ምስጠራ ፕሮቶኮልን በመጠቀም ከድረ-ገፆች ጋር በራስ-ሰር ለመገናኘት ይሞክራል።
+
+
+ የተለዩ
+
+
+ ለእነዚህ ድረ-ገጾች የሚከለክል የይዘት እገዳን አንስተዋል።
+
+ አስወግድ
+
+ ሁሉንም ድረ-ገፆች አስወግድ
+
+
+ ኩኪዎችን አግድ
+
+
+ ኩኪዎችን ማገድ ይፈልጋሉ?
+
+
+ ትር ተበላሽቷል
+
+ እናዝናለን። በዚህ ትር ላይ ችግር እያጋጠመን ነው።
+
+ እንደ የግል አሳሽ፣ ይህን ትር በፍፁም አናስቀምጥም እና ወደነበረበት መመለስ አንችልም።
+
+ ትርን ዝጋ
+
+
+ የብልሽት ሪፖርት ወደ ሞዚላ ይላኩ
+
+
+
+
+ ከ%s ጀምሮ የታገዱ መከታተያዎች
+
+ ይዘት
+
+ ማስታወቂያ
+
+ ማኅበራዊ
+
+ ትንታኔ
+
+ የተሻሻለ የመከታተያ ጥበቃ
+
+ ለዚህ ጣቢያ ጥበቃዎች ጠፍተዋል
+
+ ጥበቃዎች ለዚህ ጣቢያ በርተዋል
+
+ ግንኙነት ደህንነቱ የተጠበቀ ነው
+
+ ግንኙነት ደህንነቱ የተጠበቀ አይደለም
+
+ መከታተያዎች እና ስክሪፕቶች ለማገድ
+
+
+ ተመለስ
+
+
+
+ አስወግድ
+
+ እንደገና ይሰይሙ
+
+ እንደገና ይሰይሙ
+
+ የአቋራጭ ስም
+
+
+ የተቀመጡ እና የተጋሩ ምስሎች የ%1$s ታሪክን ሲደመስሱ <b>አይጠፉም</b>
+
+
+
+ ገጽታ
+
+ ፈካ ያለ
+
+ ጨለም ያለ
+
+ በባትሪ ቆጣቢ የተዘጋጀ
+
+ የመሳሪያውን ገጽታ ተከተል
+
+
+ ይህ ድረ-ገፅ HTTPSን አይደግፍም
+
+
+ የበለጠ ለመረዳት
+ ይህን ቅንብር በቅንብሮች> ግላዊነት & ደህንነት > ደህንነት።]]>
+
+
+ ግንኙነት ደህንነቱ የተጠበቀ አይደለም
+
+
+
+ ከዚህ ሰርቨር ጋር ከዚህ ቀደም በተሳካ ሁኔታ ከተገናኙ፤ ስህተቱ ጊዜያዊ ሊሆን ይችላል።
+ ]]>
+
+
+ አንድ ሰው ድረ-ገፁን ለማስመሰል እየሞከረ ሊሆን ይችላል እና መቀጠል አደገኛ ሊሆን ይችላል።
+
+ %1$s %2$s ን አያምንም ምክንያቱም የእውቅና ማረጋገጫ ሰጪው ስለማይታወቅ፣ የእውቅና ማረጋገጫው በራሱ የተፈረመ ነው ወይም ሰርቨሩ ትክክለኛ መካከለኛ የእውቅና ማረጋገጫዎችን እየላከ አይደለም።
+ ]]>
+
+
+
+ ትርን ዝጋ
+
+
+
+ ይዘናቸዋል! ይህ ድረ-ገፅ እርስዎን እንዳይሰልል አቁመነዋል። የምንከለክለውን ለማየት በማንኛውም ጊዜ መከለያውን ይንኩ።
+
+
+ ብቅ ባይን ዝጋ
+
+
+
+ እርስዎ ጥበቃ ይደረግልዎታል!
+
+ እነዚህ ነባሪ ቅንጅቶች ጠንካራ ጥበቃ ይሰጣሉ። ግን ልዩ ፍላጎቶችዎን ለማሟላት ቅንብሮቹን ማስተካከል ቀላል ነው።
+
+ አሰናብት
+
+
+ ሁሉንም - ታሪክን፣ ኩኪዎችን፣ ሁሉንም ነገር ለማጥፋት እዚህ ነካ ያድርጉ እና በአዲስ ትር ላይ አዲስ ይጀምሩ።
+
+
+
+
+ ዝጋ
+
+
+ የፍለጋ መግብር
+
+
+ የአሰሳ ታሪክ ጸድቷል! 🎉
+
+
+ የግል አሰሳ ክፍለ ጊዜህን ጀምር፣ እና ሲጠቀሙ መከታተያዎችን እና ሌሎች መጥፎ ነገሮችን እናግዳለን።
+
+
+ እርስዎን ከግል አሰሳዎ ጋር እንተውዎታለን፣ ነገር-ግን በሚቀጥለው ጊዜ በመነሻ ማያዎ ላይ ባለው %1$s መግብር በፍጥነት ይጀምሩ።
+
+
+ መግብርን ወደ መነሻ ማያ ገጽ ያክሉ
+
+
+ መግብር ወደ መነሻ ማያ ገጽ ታክሏል
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-an/strings.xml b/mobile/android/focus-android/app/src/main/res/values-an/strings.xml
new file mode 100644
index 0000000000..7e798cf8f6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-an/strings.xml
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+
+ Cancelar
+ OK
+
+ Alzar
+
+ Buscar u escribir adreza
+
+ Navegación privada automatica.\nNavegar. Borrar. Repetir.
+
+ S\'ha borrau lo suyo historial de navegación.
+
+ Buscar %1$s
+
+ Compartir…
+
+ Reportar problema en o puesto web
+
+ Ubrir en %1$s
+
+ Ubrir en…
+
+ Anyader a la pantalla d\'inicio
+
+ Achustes
+ Quanto a
+ Aduyar
+ Los tuyos dreitos
+
+ Trazadors blocaus
+
+ Empentau per %1$s
+
+ Compartir vía
+
+ Borrar l\'historial de navegación
+
+ Ubrir
+
+ Borrar y ubrir
+
+ Borrar
+
+ Borrar l\'historial de navegación
+
+
+ Privacidat y seguranza
+
+ Seguimiento, cookies, trías de datos
+
+ Valors per defecto, autocompletar
+
+ Quanto a %1$s, aduya
+
+ Conteniu web
+
+ Cambiar entre las aplicacions
+
+ Cheneral
+
+ Replega y uso de datos
+
+ Buscar
+
+ Per defecto
+
+ Activau
+
+ Desactivau
+
+ Autocompletar URL
+
+ + Anyader URL personalizada
+
+ Anyader URL personalizada
+
+ Anyadir una URL personalizada
+
+ Cookies y datos d\'o puesto
+
+ Trías de datos
+
+ Borrar URLs personalizadas
+
+ Saber-ne mas
+
+ Anyader y chestionar l\'autocompletau personalizau d\'URLs.
+
+ URL a anyader
+
+ Apegar u escribir URL
+
+ Eixemplo: mozilla.org
+
+ Eixemplo: eixemplo.com
+
+ Nueva URL personalizada anyadida.
+
+ Borrar
+
+ Borrar
+
+ Compreba la URL que has escrito.
+
+ Idioma
+ Predeterminau d\'o sistema
+
+ Privacidat
+ Blocar los trazadors publicitarios
+ Bells anuncios fan seguimiento d\'as visitas alo puesto, mesmo si no fas clic en os anuncios
+ Bloca los trazadors analiticos
+ Usaus pa replegar, analisar y medir actividatz como toques a la pantalla y desplazamientos de pantalles
+ Bloca los trazadors socials
+ Incrustaus en puestos web pa trazar las tuyas visitas y amostrar funcionalidatz coo los botons de compartición
+ Bloca los trazadors d\'atro conteniu
+ Activar esta opción puede provocar problemas en bellas pachinas
+ Blocar las cookies
+
+ Blocar nomás las cookies de tercers
+
+ Indetectable
+ Amagar pachinas web quan se cambie d\'aplicación
+
+ Rendimiento
+ Blocar fuents de Web
+ Puede fer que no se veigan iconos u imachens
+
+ Blocar JavaScript
+ Las pachinas se cargarán mas aprisa, pero talment no se comporten en a traza esperada
+
+ Fer que %1$s siga lo navegador per defecto
+
+ Mozilla
+ Ninviar datos d\'uso
+
+ Saber-ne mas
+
+ Mozilla s\'esfuerza per replegar nomás lo necesario pa fornir y amillorar %1$s pa totz.
+
+ Politica de privacidat
+
+ Quanto a %1$s
+
+ Motors de busqueda instalaus
+
+ Restaurar los motors de busqueda per defecto
+
+ + Anyader belatro motor de busqueda
+ Sacar motors de busqueda
+ Eliminar
+
+ Anyader motor de busqueda
+
+ Nombre d\'o motor de busqueda
+ Cadena de busqueda a emplegar
+ Alzar
+
+ Eixemplo: eixemplo.com/search/?q=%s
+
+ Nuevo motor de busqueda anyadiu.
+
+ Escribir lo nombre d\'o motor de busqueda
+ Un motor de busqueda instalau ye emplegando ya ixe nombre.
+
+ Escribe la cadena de busqueda
+
+ Compreba que la cadena de busqueda concuerda con lo formato d\'eixemplo
+
+ Borrar lo texto
+
+ Descartar
+
+ Borrar l\'historial de navegación
+
+ Pestanyas ubiertas: %1$s
+
+ Connexión segura
+
+ Se ye cargando
+
+ Puesto web cargau
+
+ Mas opcions
+
+ Abanzar en l\'historial
+
+ Esviellar la pachina
+
+ Ir enta zaga en l\'historial
+
+ Aturar la carga d\'a pachina
+
+ Tornar ta l\'aplicación anterior
+
+ Blocar trazadors
+
+ Los tuyos dreitos
+
+ Ubrir lo vinclo en belatra aplicación
+ Puetz salir d\'o %1$s pa ubrir este vinclo en %2$s.
+ Trobar una aplicación que pueda ubrir lo vinclo
+ Garra d\'as aplicacions d\'o tuyo dispositivo ha estau capable d\'ubrir este vinclo. Puetz zarrar lo %1$s pa mirar en %2$s una aplicación que lo pueda ubrir.
+ Salir d\'a Navegación Privada?
+
+ %1$s ha rematau
+
+ Ubrir
+
+
+
+
+
+
+
+
+
+ No s\'ha trobau lo servidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Refuerza la tuya privacidat
+ Leva la navegación privada ta lo siguient nivel. Bloca anuncios y atros contenius que puedan seguir-te a través d\'a web y enlenteixen la carga d\'as pachinas.
+
+ Busca a la tuya traza
+ Yes mirando bella cosa diferent? Triga belatro motor de busqueda en os Achustes.
+
+ Anyade alcorces a la tuya pantalla d\'inicio
+ Torna rapidament ta los tuyos puestos favoritos en %1$s. Triga simplement \"Anyader en a pantalla d\'inicio\" dende lo menú %1$s.
+
+ Fe d\'a privacidat un costumbre
+ Fe de %1$s lo tuyo navegador per defecto y aconsigue los beneficios d\'a navegación privada quan ubres pachinas web dende atras aplicacions.
+
+ Entendiu!
+ Blincar
+ Siguient
+
+ -
+
+ Anyader
+
+ Cancelar
+
+ Sesión de navegación privada
+
+ Las notificacions te deixar borrar la tuya sesión de %1$s d\'un toque. No te cal ubrir l\'aplicación ni amostrar lo conteniu d\'o navegador.
+
+ Borra l\'historial de navegación
+
+ Baixa-te lo Firefox
+
+ %1$s ye software libre y de codigo ubierto, feito per Mozilla y atros colaboradors.
+
+
+
+
+ Nombre d\'usuario
+ Clau
+ Borrar
+
+ Connexión segura
+ Connexión no segura
+ Verificau per: %1$s
+
+ Seguranza de puesto web
+ La URL ya existe
+
+ Trobar en a Pachina
+
+ Trobar en a pachina
+
+ %1$d/%2$d
+ %1$d de %2$d
+
+ Trobar lo siguient resultau
+ Trobar lo resultau anterior
+ Deixar de buscar en a pachina
+
+
+
+ Demandar lo puesto de sobremesa
+
+ URL copiada
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-anp/strings.xml b/mobile/android/focus-android/app/src/main/res/values-anp/strings.xml
new file mode 100644
index 0000000000..83cdf11f64
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-anp/strings.xml
@@ -0,0 +1,730 @@
+
+
+
+
+
+
+
+
+ रद्द करियै
+
+ ठीक
+
+ सहेजियै
+
+
+ खोजियै अथवा पता भरियै
+
+ स्वचालित निजी ब्रॉउजिंग.ब्रॉउज. मेटैइयै. दोहरैइयै.
+
+
+ अपने के ब्रॉउजिंग इतिहास मेटाए देलो गेलए.
+ ब्राउज़िंग इतिहास मेटाल्य गेलै
+
+
+ टैब्स के ब्रॉउजिंग इतिहास मेटाए देलो गेलए.
+
+
+ %1$s लेली खोजियै
+
+
+ शेयर…
+
+
+ साइट क रिपोर्ट करियै
+
+
+ %1$s में खोलियै
+
+
+ एकरा में खोलियै…
+
+
+ होम स्क्रीन में जोड़ियै
+
+ सेटिंग्स
+ के संबंध में
+ मदत
+ अपने के अधिकार
+
+
+ ट्रेकर्स ब्लॉक भेलै
+
+
+ कुछ साइटों के ठीक करै ले बंद करियै
+
+
+ %1$s के द्वारा चलाएल जाए रहलो छै
+
+
+ एकरा द्वारा शेयर करियै
+
+
+ ब्राउज़िंग इतिहास मेटैइयै
+
+
+ खोलियै
+
+
+ मेटैइयै आरू खोलियै
+
+
+ मेटैइयै
+
+
+ ब्राउज़िंग इतिहास मेटैइयै
+
+
+
+ मेटैइयै आरू खोलियै
+
+
+ %1$s मेटैइयै आरू खोलियै
+
+
+
+ गोपनियता आरु सुरक्षा
+
+
+ ट्रैकिंग, कुकीज़, डेटा चुनियै
+
+
+ डिफॉल्ट सेट करियै, आपनो सऽ पूर्न
+
+
+
+
+ %1$s कऽ बारो मे, मदद
+
+
+ वेब सामग्री
+
+
+ स्विचिंग एप्स
+
+
+ सामान्य
+
+
+ डेटा संग्रह आरु उपयोग
+
+ खोजियै
+
+
+ खोज सुझाव प्राप्त करियै
+
+ आपनो के सर्च ईंजन के एड्रेस बार म जे टाइप करबे %1$s उकरा भेज देत
+
+
+ डिफ़ॉल्ट
+
+
+ सर्च ईंजन
+
+
+ चालू
+
+
+ बंद
+
+
+ URL अपना सँ पूर्ण
+
+
+ सर्वोच्च साइट्स खातिर
+
+
+ एड्रेस बार में 450 लोकप्रिय URLs पर %1$s स्वत: पूर्णता लेली सक्षम करू
+
+
+ साइटों ले आपने जोड़ियै
+
+
+ साइटों के प्रबंध करियै
+
+
+ साइटों के प्रबंध करियै
+
+
+ + अपन पसंदीदा URL जोड़ियै
+
+
+ URL जोड़ियै
+
+
+ पसंदीदा URL जोड़ियै
+
+
+ पसंदीदा URL जोड़ियै
+
+
+ अपना सँ पूर्ण खातिर लिंक जोड़ियै
+
+
+ कुकीज़ आरु साइट डेटा
+
+
+ डेटा चुनियै
+
+
+ पसंदीदा URLs जोड़ियै
+
+
+ बेसी जानकारी
+
+
+ जोड़ियै आरु मैनेज करियै पसंदीदा अपना सँ पूर्ण URLs.
+
+
+ जोड़ए लेली URL
+
+
+ URL पेस्ट करियै या भरियै
+
+
+ उदाहरण: mozilla.org
+
+
+ उदाहरण: example.com
+
+
+ नया उपभोक्ता पसंदीदा URL जोड़लोऽ गेलै.
+
+
+ हटैइयै
+
+
+ हटैइयै
+
+
+ जे URL भरने छथिन ओकरा फिनु सँ जाँच करियै.
+
+ भाषा
+
+ डिफ़ॉल्ट सिस्टम
+
+ गोपनीयता
+ विज्ञापन ट्रैकर्स कs ब्लॉक करियै
+ कुछ विज्ञापन साइट विजिट ट्रैक करै छै, भले अपने विज्ञापन पर क्लिक नै करै छी
+ एनालेटिक ट्रैकर्स ब्लौक करियै
+ इकट्ठा करै लऽ, विश्लेषण और मापे लऽ उपयोग करलो जाय छै
+ सोशल ट्रैकर्स ब्लौक करियै
+ साइट पर अपने के आबै जाए पर निगरानी राखए आरू शेयर बटन जेहन कार्यक्षमता प्रदर्शित करए लेली अंत:स्थापित
+ दोसरो कोंटेंट ट्रैकर्स ब्लौक करियै
+ सक्षम करै सऽ कुछ पृष्ठ के व्यवहार अप्रत्याशित हुए सकै छै
+ कुकीज ब्लौक करियै
+
+
+ नै, धन्यवाद
+ खाली 3र्ड पार्टी ट्रैकर कूकीज ब्लौक करियै
+ खाली 3र्ड पार्टी कूकीज ब्लौक करियै
+
+
+ ऐप अनलॉक करै ले फिंगरप्रिंट क प्रयोग करियै
+
+
+ गोपनीयता
+
+ ऐप्स क स्विच करै समय आरु स्क्रीनशॉट लै पर ब्लोक करै समय वेबपेज नुकैइयै.
+
+ सुरक्षा
+
+ प्रदर्शन
+ वेब फ्रंट ब्लौक करियै
+
+ आइकन अथवा छवि गुम हुए सकै छै
+
+ " JavaScript ब्लौक करियै"
+
+ पेज तेजी सँ लोड हुए सकै छै, मगर अनजान व्यवहार भी करै सकै छै
+
+
+ %1$s कऽ डिफ़ॉल्ट ब्रॉजर बनैइयै
+
+ Mozilla
+ उपयोग डेटा भेजियै
+
+
+ बेसी जानकारी
+
+
+ Mozilla केवल ओ चीज के जमा करै छै जकरा %1$s मे सभक लेली सुधार करै केरो आवश्यक्ता छै.
+
+
+ गोपनीयता सूचना
+
+
+ %1$s के बारे मे
+
+
+ सर्च ईंजन इंसटॉल करलो गेलै
+
+
+ सर्च ईंजन चुनियै
+
+
+ डिफॉल्ट सर्च इंजन के रिस्टोर करियै
+
+
+ + अन्य सर्च इंजन जोड़ियै
+ सर्च ईंजन हटैइयै
+ हटैइयै
+
+
+ दोसरो सर्च इंजन जोड़ियै
+
+
+ सर्च ईंजन जोरियै
+
+ सर्च ईंजन नाम
+ उपयोग लेली सर्च स्ट्रिंग
+ सहेजियै
+
+
+ उदाहरण: example.com/search/?q=%s
+
+ नया सर्च ईंजन जोड़लो गेल.
+
+ सर्च ईंजन केरो नाम भरियै
+ एक ठो इंस्टॉल करल सर्च इंजन पहिने सs ओए नाम के उपयोग करि रहलो छै.
+
+ सर्च स्ट्रिंग भरियै
+
+ जाँचू जे सर्च स्ट्रिंग उदाहरण प्रारूप सँ मेल करै छै
+
+
+ इनपुट मेटैइयै
+
+
+ रद्द करियै
+
+
+ ब्राउजिंग इतिहास मेटैइयै
+
+
+ %1$s टैब्स खोलियै
+
+
+ सुरक्षित कनेक्सन
+
+
+ लोड भ्या रहलो छै
+
+
+ वेबसाईट लोड भेलै
+
+
+ अधिक विकल्प
+
+
+ अधिक विकल्प बटन
+
+
+ आगाँ मार्गनिर्देशित करियै
+
+
+ वेबसाईट फेनु लोड करियै
+
+
+ पाछाँ मार्गनिर्देशित करियै
+
+
+ लोड भ्या रहलो वेबसाईट के रोकियै
+
+
+ पिछला ऐप पर वापस घुरियैs
+
+
+ ब्लॉक भेल ट्रैकर्स के संख्या
+
+
+ ट्रेकर्स ब्लॉक करियै
+
+ अपने के अधिकार
+
+ दोसर एप मे लिंक खोलियै
+
+ अपने लिंक %2$s में खोलै लेली %1$s छोड़ि सकै छी.
+
+ एकठो ऐप खोजू जे लिंक खोलि सकैत छै
+
+ अपने के डिवाइस केरो कोनो ऐप ई लिंक के खोलि नै सकै छै. अपने ऐप %2$s खोजै लेली %1$s छोड़ि सकै छी जे ऐप खोजि सकै छै.
+
+ निजी ब्राउज़िंग से बाहर निकलब?
+
+
+ %1$s समाप्त भेलै
+
+
+ खोलियै
+
+
+
+
+
+
+
+
+
+ सर्वर नय भेटल
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ बंद करियै
+
+
+
+ %1$s रो स्वागत छै
+
+
+ डिफ़ॉल्ट ब्राउज़र सेट करो
+
+
+ छोड़ियै
+
+
+ अपन गोपनीयता के बढ़ैइयै
+
+ निजी ब्राउज़िंग के अगिला स्तर धरि लिअ. विज्ञापन आरू आन सामग्री के ब्लॉक करू जे अहाँक सभ साइट के ट्रैक करि सकै छै आरू पृष्ठ लोड समय के बढ़ाए दै छै.
+
+
+ अपने के खोज, अपने के रस्ता
+
+ कुछ अलग खोजि रहलो छी? सेटिंग्स में दोसर डिफ़ॉल्ट खोज इंजन चुनियै.
+
+
+ अपन मुख्य स्क्रीन पर शॉर्टकट जोड़ियै
+
+ %1$s में अपन पसंदीदा साइट पर तेज़ी सँ वापस जइयै. बस %1$s मेनू से \"मुख्य स्क्रीन पर जोड़ियै\" चुनियै.
+
+
+ गोपनीयता अपन आदत बनैइयै
+
+ %1$s क अपनो तयशुदा ब्राउज़र रूप में सेट करू आरू जखन अहाँ आन ऐप सँ वेबपृष्ठ खोलै छी तब निजी ब्राउज़िंग केरो फायदा प्राप्त करियै.
+
+ ठीक, बुझि गेलियै!
+ छोड़ियै
+ अगिला
+
+
+ -
+
+
+ जोड़ियै
+
+
+ हा
+
+
+ रद्द करियै
+
+
+ ना
+
+
+ निजी ब्राउज़िंग सत्र
+
+
+ अधिसूचनाएँ अपने क अपन %1$s सत्र एक टैप सँग मेटाबै दय छै. अपने के ऐप खोलए या ब्राउज़र में कथि भ्या रहलो छै, देखए केरो ज़रूरत नय छै.
+
+
+ ब्राउज़िंग इतिहास मेटैइयै
+
+
+ Firefox डाउनलोड करियै
+
+
+
+
+
+
+
+
+ मोज़िला सार्वजनिक लाइसेंस आरू आन मुक्त स्रोत लाइसेंस केरो नियम के अंतर्गत अहाँ के उपलब्ध करैलो जाए छै.]]>
+
+
+ लाइसेन्स केरो अंतर्गत उपलब्ध छै.]]>
+
+
+ यूजरनाम
+ पासवर्ड
+ हटैइयै
+
+
+
+ सुरक्षित कनेक्शन
+ असुरक्षित कनेक्शन
+
+ %1$s द्वारा सत्यापित
+
+
+ साइट सुरक्षा
+ URL पहले से मौजूद छै
+
+
+ पेज मs खोजिइयै
+
+
+ पेज मs खोजिइयै
+
+
+ %1$d/%2$d
+
+ %2$d सs %1$d
+
+
+ अगलो परिणाम जानिइयै
+
+ पिछ्लो परिणाम जानिइयै
+
+ पेज में रद्द करियै
+
+
+
+
+ डेस्कटॉप साइट लs आग्रह करियै
+
+
+ URL कॉपी भेलऽ
+
+
+ डेवलपर उपकरण
+
+
+ विकसित करल गेल
+
+
+ चालू
+
+
+ बंद
+
+
+ रद्द करियै
+
+
+ सेटिंग्स
+
+
+ सेटिंग्स में जाहो
+
+
+ स्थान
+
+
+ बेसी जानकारी
+
+
+ हटैइयै
+
+
+ पूरा भेल
+
+
+ USB/Wi-Fi के द्वारा रिमोट डीबगिंग
+
+
+ नए सत्र में लिंक खोलियै
+
+
+ फिंगरप्रिंट आइकन
+
+
+ फिंगरप्रिंट नै पहचानल गेल छकै. फेनु प्रयास करियै.
+
+
+ फिंगर बहुत तेज छ्कै. फेनु प्रयास करियै.
+
+
+ खोज सुझाव देखबै?
+
+
+ नैय
+
+
+ हौं
+
+
+ कुछ सर्च ईंजन सुझाव नै दिखाय सकै छै.
+
+
+ रद्द करियै
+
+
+
+
+ कि साइट अप्रत्याशित रूप से व्यवहार कैर रहल छै?\n ट्रैकिंग सुरक्षा बंद करै के प्रयास करियै
+
+
+ होम स्क्रीन में जोरियै]]>
+
+
+ %1$s में सब लिंक खोलियै\n डिफ़ॉल्ट ब्राउज़र के रूप में %1$s सेट करियै
+
+
+ सबसे जादा उपयोग करै वाला साइटों के अपना सँ पूर्ण URLs ले\n एड्रेस बार में कोनो URL के देर तक दबियै
+
+
+ नया टैब में एगो लिंक खोलियै\n पेज पर कोनो लिंक के देर तक दाबियै
+
+
+ प्रारंभ स्क्रीन सलाह के बंद करियै
+
+
+ नया टैब खुलल
+
+
+ बदलियै
+
+
+ अपवाद
+
+ हटैइयै
+
+
+ सब वेबसाईट हटैइयै
+
+
+ कुकीज ब्लौक करियै
+
+
+ टैब बंद करियै
+
+
+
+
+ सामाजिक
+
+
+ पिछू जाइयै
+
+
+
+ हटैइयै
+
+
+ नाम बदलियै
+
+ नाम बदलियै
+
+
+ सहेजलो गेल आरू शेयर कएल गेल छवि <b>हटाएलो नय जाएत</b> जखन अहाँ %1$s इतिहास के मेटाएबै
+
+
+
+ थीम
+
+ हल्का
+
+ गहरा
+
+
+
+ टैव बंद करियै
+
+
+ रद्द करियै
+
+
+
+
+ बंद करियै
+
+
+ ब्राउज़िंग इतिहास मेटाल्य गेलै!
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ar/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000000..b736d40c6a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,839 @@
+
+
+
+
+
+
+
+
+ ألغِ
+
+ حسنا
+
+ احفظ
+
+
+ ابحث أو أدخل عنوانا
+
+ تصفح خاص تلقائي.\nتصفح. امسح. كرر.
+
+
+ لقد مسح تأريخ تصفحك.
+ مُسح تأريخ التصفح
+
+
+ مُسح تأريخ التصفّح للسان.
+
+
+ ابحث عن %1$s
+
+
+ شارك…
+
+
+ أبلِغ عن مشكلة بالموقع
+
+
+ افتح في %1$s
+
+
+ افتح في…
+
+
+ أضِف إلى شاشة البداية
+
+
+ أضِف إلى الاختصارات
+
+ أزِل من الاختصارات
+
+ الإعدادات
+ عن
+ مساعدة
+ حقوقك
+
+
+ المتعقبات المحجوبة
+
+
+ يمكن بتعطيل هذا إصلاح مشاكل بعض المواقع
+
+
+ حجب المحتوى
+
+ عطّله لإصلاح علل بعض المواقع
+
+
+ تدعمها %1$s
+
+
+ شارك عبر
+
+
+ امسح تأريخ التصفح
+
+
+ افتح
+
+
+ امسح و افتح
+
+
+ امسح
+
+
+ امسح تأريخ التصفح
+
+
+
+ امسح وافتح
+
+
+ امسح وافتح %1$s
+
+
+
+ يعطيك %1$s الدفّة.
+استعمله كمتصفّح خاص:
+
+ ابحث وتصفّح مباشرة في التطبيق
+ احجب المتعقّبات (أو حدّث الإعدادات للسماح بها)
+ امسح الكعكات بالإضافة إلى حذف تأريخ البحث والتصفّح
+
+
+%1$s منتج من Mozilla. مهمتنا تكمن في التشجيع نحو إنترنت مفتوح وصحّي.
+اطّلع على المزيد
]]>
+
+
+ الخصوصية والسريّة
+
+
+ التّعقّب، و الكعكات، و اختيار البيانات
+
+
+ الضبط كمبدئي والإكمال التلقائي
+
+
+
+
+ عن %1$s والمساعدة
+
+
+ الحماية الموسّعة من التعقب
+
+
+ محتوى الوب
+
+
+ التبديل بين التطبيقات
+
+
+ عام
+
+
+ المتصفّح المبدئي، اللغة
+
+
+ تجميل البيانات واستخدامها
+
+ البحث
+
+
+ اجلب اقتراحات البحث
+
+ سيُرسل %1$s ما تكتبه في شريط العنوان إلى محرك البحث الذي فضّله
+
+
+ المبدئي
+
+
+ محرك البحث
+
+
+ مفعّل
+
+
+ معطّل
+
+
+ إكمال المسارات تلقائيا
+
+
+ لأكثر المواقع زيارة
+
+
+ فعّله ليُكمل %s تلقائيا أكثر من ٤٥٠ عنوان وِب في شريط العناوين.
+
+
+ للمواقع التي تُضيفها
+
+
+ فعّله ليُكمل %s تلقائيا المسارات في المفضّلة.
+
+
+ أدِر المواقع
+
+
+ أدِر المواقع
+
+
+ + أضِف مسارا مخصصا
+
+
+ قائمة مخصّصة من الإكمال التلقائي:
+
+
+ أضِف مسارًا
+
+
+ أضِف مسارا مخصصا
+
+
+ أضِف مسارا مخصصا
+
+
+ أضِف رابطا لإكماله تلقائيا
+
+
+ الكعكات و بيانات المواقع
+
+
+ اختيارات البيانات
+
+
+ أزِل المسارات المخصصة
+
+
+ اطّلع على المزيد
+
+
+ أضِف وأدِر إكمال المسارات المخصصة تلقائيا.
+
+
+ المسار لإضافته
+
+
+ ألصِق مسارا أو أدخِله
+
+
+ مثال: mozilla.org
+
+
+ مثال: example.com
+
+
+ أُضيف مسار مخصص جديد.
+
+
+ أزِل
+
+
+ أزِل
+
+
+ تحقق من المسار الذي أدخلته.
+
+ اللغة
+
+ المبدئية للنظام
+
+ الخصوصية
+ احجب متعقبات الإعلانات
+ تتعقّب بعض الإعلانات زياراتك إلى المواقع، حتى إن لم تنقرها
+ احجب متعقبات التحليلات
+ تُستخدم لجمع نشاطاتك وتحليلها وقياسها، مثل النقر وتمرير الصفحات
+ احجب المتعقبات الاجتماعية
+ تُضمّن في المواقع لتتعقب زياراتك ولعرض بعض الخصائص مثل أزرار المشاركة
+ احجب متعقبات المحتوى الأخرى
+ تفعيل الخيار قد يتسبب بألا تعمل بعض الصفحات كما يجب
+ احجب الكعكات
+
+
+ لا، شكرًا
+ احجب كعكات المتعقّبات من الأطراف الثالثة فحسب
+ احجب الكعكات من الأطراف الثالثة فحسب
+ احجب الكعكات بين المواقع
+ نعم، أودّ ذلك
+
+
+ استخدم البصمة لإلغاء قفل التطبيق
+
+
+ التخفّي
+
+ أخفِ صفحات الوب عند الانتقال بين التطبيقات وامنع التقاط الشاشة.
+
+ الأمان
+
+ الأداء
+ احجب خطوط الوب
+
+ قد يسبب اختفاء بعض الأيقونات أو الصور
+
+ احجب جافاسكرِبت
+
+ قد تتحمّل الصفحات بشكل أسرع، ولكنها قد لا تعمل كما ينبغي أيضا
+
+
+ اجعل %1$s متصفّحك المبدئي
+
+ موزيلا
+ أرسل بيانات الاستخدام
+
+
+ اطّلع على المزيد
+
+
+ تحاول موزيلا ألا تجمع أكثر مما تحتاجه لإتاحة %1$s و تحسينه للجميع.
+
+
+ تنويه الخصوصية
+
+
+ عن %1$s
+
+
+ محركات البحث المثبتة
+
+
+ اختر محرك البحث
+
+
+ استعد محركات البحث المبدئية
+
+
+ + أضف محرك بحث آخر
+ أزِل محركات البحث
+ أزِل
+
+ أضِف محرك بحث آخر
+
+ اختر محرك البحث المفضل لك:
+
+
+ أضف محرك بحث
+
+ اسم محرك البحث
+ نص البحث المستخدم
+ احفظ
+
+
+ مثال: example.com/search/?q=%s
+
+ أُضيف محرك البحث الجديد.
+
+ أدخِل اسم محرك البحث
+ هناك محرك بحث مثبّت يحمل نفس الاسم بالفعل.
+
+ أدخِل نص البحث
+
+ تحقق من أن نص البحث يطابق تنسيق المثال
+
+
+ امسح الإدخال
+
+
+ غادِر
+
+
+ امسح تأريخ التصفح
+
+
+ الألسنة المفتوحة: %1$s
+
+
+ اتصال آمن
+
+
+ يُحمّل
+
+
+ تم تحميل الموقع
+
+
+ المزيد من الخيارات
+
+
+ زر ”خيارات أخرى“
+
+
+ تصفّح للأمام
+
+
+ أعِد تحميل الموقع
+
+
+ تصفّح للوراء
+
+
+ أوقف تحميل الموقع
+
+
+ ارجع للتطبيق السابق
+
+
+ عدد المتعقّبات المحجوبة
+
+
+ احجب المتعقّبات
+
+ حقوقك
+
+ افتح الرابط في تطبيق آخر
+
+ يمكنك مغادرة %1$s لفتح هذا الرابط في %2$s.
+
+ ابحث عن تطبيق يدعم فتح الرابط
+
+ لا يوجد أي تطبيق على جهازك يدعم فتح هذا الرابط. يمكنك مغادرة %1$s للبحث في %2$s عن تطبيق يدعم الرابط.
+
+ أتريد مغادرة التصفح الخاص؟
+
+
+ اكتمل %1$s
+
+
+ افتح
+
+
+
+
+
+
+
+
+
+ الخادوم غير موجود
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ حسّن خصوصيتك
+
+ انتقل بالتصفّح الخاص إلى المستوى التالي. احجب الإعلانات وغيرها من المحتويات التي تتعقبك أثناء التصفح وتأخذ من وقت تحميل الصفحات.
+
+
+ تبحثُ بأسلوبك أنت
+
+ أتبحث عن شيء مختلف؟ اختر محرّك بحث آخر ليكون المبدئي من الإعدادات.
+
+
+ أضف اختصارات إلى شاشة البداية
+
+ عُد إلى مواقعك المفضلة في %1$s بسرعة. اختر ”أضف إلى شاشة البداية“ من قائمة %1$s.
+
+
+ اجعل من الخصوصية عادة
+
+ اضبط %1$s ليكون المتصفّح المبدئي وتمتع بفوائد التصفح الخاص عندما تفتح موقعا من تطبيق آخر.
+
+ حسنًا، فهمت
+ تخطّ
+ التالي
+
+
+ -
+
+
+ أضِف
+
+ نعم
+
+
+ ألغِ
+
+ لا
+
+
+ سيُفتح الاختصار مع تعطيل الحماية الموسّعة من التعقب
+
+
+ جلسة تصفح الخاص
+
+
+ تتيح لك التنبيهات مسح جلسة %1$s بضغطة واحدة. لا تحتاج إلى فتح التطبيق أو رؤية ما المعروض فيه.
+
+
+ امسح تأريخ التصفح
+
+
+ نزّل فَيرفُكُس
+
+
+
+
+
+
+
+
+ رخصة موزيلا العامة وترخيصات المصادر المفتوحة الأخرى.]]>
+
+
+ من هنا.]]>
+
+
+ الرخص الحرة والمفتوحة المصدر.]]>
+
+
+ غنو العمومية الإصدارة الثالثة، وهي متاحة هنا .]]>
+
+
+ اسم المستخدم
+ كلمة السر
+ امسح
+
+
+
+ اتّصال آمن
+ اتّصال غير آمن
+
+ تثبّت منها: %1$s
+
+
+ أمن الموقع
+ المسار موجود بالفعل
+
+
+ ابحث في الصفحة
+
+
+ ابحث في الصفحة
+
+
+ %1$d/%2$d
+
+ %1$d من أصل %2$d
+
+
+ ابحث عن النتيجة التالية
+
+ ابحث عن النتيجة السابقة
+
+ أخفِ لوحة البحث في الصفحة
+
+
+
+
+ اطلب موقع سطح المكتب
+
+
+ نسخة سطح مكتب
+
+
+ نُسِخَ المسار
+
+
+ أدوات المطوّرين
+
+
+ افتح الروابط في التطبيقات
+
+
+ متقدم
+
+
+ تصاريح المواقع
+
+
+ التشغيل التلقائي
+
+
+ اسمح بالصوت والڤِديو
+
+
+ احجب الصوت فقط
+
+
+ يُنصح به
+
+
+ احجب الصوت والڤِديو
+
+
+ الدراسات
+
+
+ قد يثبّت Firefox بعض الدراسات ويشغّلها من وقت لآخر.
+
+
+ اعرف المزيد
+
+
+ سيُغلق التطبيق لتطبيق التغييرات
+
+
+ أزِل
+
+
+ مفعّلة
+
+
+ مكتملة
+
+
+ التنقيح عن بعد عبر واي-فاي/USB
+
+
+ افتح الرابط في جلسة جديدة
+
+
+ أيقونة البصمة
+
+
+ فشل التعرف على البصمة. أعِد المحاولة.
+
+
+ حرّكت إصبعك بسرعة. أعِد المحاولة.
+
+
+ أتريد عرض اقتراحات البحث؟
+
+
+ لتستلم الاقتراحات، يحتاج %1$s إلى إرسال ما تكتبه في شريط العنوان إلى محرك البحث.
+
+
+ لا
+
+
+ نعم
+
+
+ لا تعرض بعض محركات البحث أية اقتراحات.
+
+
+ أهمِل
+
+
+
+
+ أترى أن سلوك الموقع غريب؟\nجرّب تعطيل الحماية من التعقّب
+
+
+ أضِف إلى شاشة البداية]]>
+
+
+ افتح كل الروابط في %1$s\nاضبط %1$s ليكون المتصفح المبدئي
+
+
+ أكمِل تلقائيًا عناوين المواقع التي تزورها باستمرار\nاضغط مطوّلًا على أي عنوان في شريط العنوان
+
+
+ افتح الرابط في لسان جديد\nانقر مطوّلًا على أي رابط في الصفحة
+
+
+ عطّل النصائح في شاشة البداية
+
+
+ فُتِح لسان جديد
+
+
+ انتقل
+
+
+ انتقل إلى الرابط في اللسان حالا
+
+
+ احجب المواقع التي قد تكون ضارة وخدّاعة
+
+ احجب المواقع التي لها بلاغ بأنها ضارة أو هجومية، أو فيها ما هو ضار أو ما هو غير مرغوب من برمجيات.
+
+
+ وضع HTTPS فقط
+
+ يحاول الاتصال بالمواقع تلقائيا باستعمال بروتوكول التعمية HTTPS لمزيد من الأمان.
+
+
+ الاستثناءات
+
+ عطّلت حجب المحتوى في هذه المواقع.
+
+ أزِل
+
+ أزِل كل مواقع الوِب
+
+
+ احجب الكعكات
+
+
+ أتريد حجب الكعكات؟
+
+
+ انهار اللسان
+
+ نأسف. نُواجه مشكلة مع هذا اللسان.
+
+ باعتبار هذا متصفح خاص، لن نحفظ هذا اللسان ولا نقدر على استعادته.
+
+ أغلِق اللسان
+
+
+
+
+
+ أرسِل تقرير الانهيار إلى موزيلا
+
+
+
+
+ المتعقّبات المحجوبة منذ %s
+
+ المحتوى
+
+ الإعلانات
+
+ التواصل الاجتماعي
+
+ التحليلات
+
+ الحماية الموسّعة من التعقب
+
+ عُطّلت الحماية في هذا الموقع
+
+ فُعّلت الحماية في هذا الموقع
+
+ الاتصال آمن
+
+ الاتصال غير آمن
+
+
+ المتعقّبات والسكربتات التي تريد حجبها
+
+
+ انتقل للخلف
+
+
+
+ أزِل
+
+
+ غيّر الاسم
+
+ غيّر الاسم
+
+ اسم الاختصار
+
+
+ الصور المحفوظة و المُشاركة <b>لن تُحذف</b> عندما تمسح تأريخ %1$s
+
+
+
+ السمة
+
+ فاتحة
+
+ داكنة
+
+ حسب وضع توفير الطاقة
+
+ اتبع سمة الجهاز
+
+
+
+ أغلِق اللسان
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ast/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ast/strings.xml
new file mode 100644
index 0000000000..82d97ab732
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ast/strings.xml
@@ -0,0 +1,490 @@
+
+
+
+
+
+
+
+
+ Encaboxar
+
+ D\'acuerdu
+
+ Guardar
+
+
+ Busca o introduz dalguna direición
+
+ Restolar en privao automático.\nRestola. Balera. Repiti.
+
+
+ Borróse l\'historial de restolar.
+
+
+ Buscar «%1$s»
+
+
+ Compartir…
+
+
+ Informar de fallu del sitiu
+
+
+ Abrir en %1$s
+
+
+ Abrir en…
+
+
+ Amestar a la pantalla d\'aniciu
+
+ Axustes
+ Tocante a
+ Ayuda
+ Los tos drechos
+
+
+ Rastrexadores bloquiaos
+
+
+ Cola potencia de %1$s
+
+
+ Compartir per
+
+
+ Balerar historial de restolar
+
+
+ Abrir
+
+
+ Balerar y abrir
+
+
+ Balerar
+
+
+ Balerar historial de restolar
+
+
+
+ Privacidá y seguranza
+
+
+ Rastrexu, cookies y escoyetes de datos
+
+
+ Predeterminación, autocompletáu
+
+
+
+
+ Tocante a %1$s, ayuda
+
+
+ Conteníu web
+
+
+ Cambéu d\'aplicaciones
+
+
+ Xeneral
+
+
+ Recoyida y usu de datos
+
+ Gueta
+
+
+ Consiguir suxerencies de gueta
+
+ %1$s va unviar lo que teclexes na barra de direiciones al motor de gueta
+
+
+ Por defeutu
+
+
+ Sí
+
+
+ Non
+
+
+ Autocompletáu d\'URLs
+
+
+ + Amestar URL personalizada
+
+
+ Amestar URL personalizada
+
+
+ Amestar URL personalizada
+
+
+ Cookies y datos del sitiu
+
+
+ Escoyetes de datos
+
+
+ Desaniciar URL personalizada
+
+
+ Deprendi más
+
+
+ Amiesta o xestiona URLs personalizaes d\'autocompletáu.
+
+
+ URL p\'amestar
+
+
+ Apega o introduz la URL
+
+
+ Exemplu: mozilla.org
+
+
+ Exemplu: exemplu.com
+
+
+ Amestóse una URL nueva personalizada.
+
+
+ Desaniciar
+
+
+ Desaniciar
+
+
+ Volvi comprobar la URL qu\'introduxesti.
+
+ Llingua
+
+ Predeterminada del sistema
+
+ Privacidá
+ Bloquiar rastrexadores publicitarios
+ Dellos anuncios rastrexen los sitios qu\'andes, incluso si nun faes clic nellos
+ Bloquiar rastrexadores d\'analítiques
+ Úsase pa recoyer, analizar y midir actividaes como calcos y desplazamientos
+ Bloquiar rastrexadores sociales
+ Intégrase en sitios pa rastrexar les tos visites y amosar funcionalidaes como botones de compartición
+ Bloquiar otros rastrexadores
+ Activalo quiciabes faiga que delles páxines se comporten de mou inesperáu
+ Bloquéu de cookies
+
+ Bloquiar namái cookies de terceros
+
+
+ Usa la buelga pa desbloquiar l\'aplicación
+
+
+ Invisible
+
+ Anubre les páxines web al camudar d\'aplicaciones y bloquia la fechura de caputres de pantalla.
+
+ Seguranza
+
+ Rindimientu
+ Bloquiar fontes web
+
+ Pue facer que falten iconos o imáxenes
+
+ Bloquiar JavaScript
+
+ Les páxines podríen cargar aína pero tamién podríe comportase de mou inesperáu
+
+
+ Predeterminar %1$s
+
+ Mozilla
+ Unviar datos d\'usu
+
+
+ Deprendi más
+
+
+ Mozilla esfuérciase en recoyer namái lo que precisa pa fornir y ameyorar %1$s pa tol mundu.
+
+
+ Avisu de privacidá
+
+
+ Tocante a %1$s
+
+
+ Motores de gueta instalaos
+
+
+ Reafitar motores de gueta
+
+
+ + Amestar otru motor de gueta
+ Desaniciar motores de gueta
+ Desaniciar
+
+
+ Amestar motor de gueta
+
+ Nome del motor de gueta
+ Cadena de gueta a usar
+ Guardar
+
+
+ Exemplu: exemplu.com/gueta/?q=%s
+
+ Amestóse\'l motor de gueta nuevu.
+
+ Introduz el nome del motor de gueta
+ Un motor de gueta instaláu yá ta usando esi nome.
+
+ Introduz la cadena de gueta
+
+ Comprueba que la cadena de gueta concase col formatu del exemplu
+
+
+ Llimpiar entrada
+
+
+ Escartar
+
+
+ Balerar historial de restolar
+
+
+ Llingüetes abiertes: %1$s
+
+
+ Conexón segura
+
+
+ Cargando
+
+
+ Sitiu web cargáu
+
+
+ Más opciones
+
+
+ Dir haza alantre
+
+
+ Recargar sitiu web
+
+
+ Restolar p\'atrás
+
+
+ Parar carga del sitiu web
+
+
+ Volver a l\'aplicación previa
+
+
+ Númberu de rastrexadores bloquiaos
+
+
+ Bloquia los rastrexadores
+
+ Los tos drechos
+
+ Apertura d\'enllaz n\'otra aplicación
+
+ Pues colar de %1$s p\'abrir esti enllaz en %2$s.
+
+ Alcuentra una aplicación que puea abrir l\'enllaz
+
+ Denguna aplicación del to preséu ye abrir esti enllaz. Pues colar de %1$s y guetar en %2$s una aplicación compatible.
+
+ ¿Colar de restolar en privao?
+
+
+ %1$s finó
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+ Nun s\'alcontró\'l sirvidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Potencia la to privacidá
+
+ Lleva\'l restolar en privao a un nivel meyor. Bloquia anuncios y otru conteníu que pue rastrexate nes páxines y faen lentos los tiempos de carga.
+
+
+ Guetes al to xeitu
+
+ ¿Guetes daqué diferente? Escueyi otru motor de gueta predetermináu nos axustes.
+
+
+ Amiesta atayos a la to pantalla d\'aniciu
+
+ Volvi aína a los tos sitios favoritos en %1$s. Namái esbilla «Amestar a la pantalla d\'aniciu» nel menú de %1$s.
+
+
+ Fai de la privacidá un vezu
+
+ Predetermina %1$s como\'l to restolador y consigui los beneficios del restolar en privao cuando abres páxines web dende otres aplicaciones.
+
+ ¡Val, coyílo!
+ Saltar
+ Siguiente
+
+
+ -
+
+
+ Amestar
+
+
+ Encaboxar
+
+
+ Sesión de restolar en privao
+
+
+ Los avisos déxente balerar la to sesión de %1$s con un toque. Nun precises abrir l\'aplicación pa ver lo que ta n\'execución nel to restolador.
+
+
+ Balerar historial de restolar
+
+
+ Baxar Firefox
+
+
+
+
+
+
+
+
+ Nome d\'usuariu
+ Contraseña
+ Llimpiar
+
+
+
+ Conexón segura
+ Conexón insegura
+
+ Verificáu por: %1$s
+
+
+ Seguranza del sitiu
+ Yá esiste la URL
+
+
+ Alcontrar na páxina
+
+
+ Alcontrar na páxina
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Non
+
+
+ Sí
+
+
+ Dalgunos motores de gueta nun puen amosar suxerencies.
+
+
+ Escartar
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ay/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ay/strings.xml
new file mode 100644
index 0000000000..7544ae9e64
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ay/strings.xml
@@ -0,0 +1,476 @@
+
+
+
+
+
+
+
+
+ Suyt\'ayaña
+
+ Walikiwa
+
+ Imaña
+
+
+ Thaqtam jan ukax imayluma
+
+ Justupach jark\'as thaqtaña. Thaqhaña. Pichsuña. Mayampi.
+
+
+ Taqpach thaqhatanakamax pichsutawa.
+
+
+ Thaqtam %1$s
+
+
+ Ch\'iqiyaña
+
+
+ Llika janjat yatiyaña
+
+
+ Jist\'aram %1$s
+
+
+ Jist\'aram…
+
+
+ Janja llikar yapkatam
+
+ Mayjt\'ayaña
+ Ukxata
+ Yanapa
+ Yäqayasiñataki
+
+
+ Thaqhañanakax jark\'antatawa
+
+
+ Ukamp ch\'amañchatawa %1$s
+
+
+ Uka tuq ch\'iqiyaña
+
+
+ Thaqhañ imatanak pichsuña
+
+
+ Jist\'araña
+
+
+ Pichsuña ukat jist\'araña
+
+
+ Pichsuña
+
+
+ Thaqhañ imatanak pichsuña
+
+
+
+ Imantatäña ukatx jark\'atäña
+
+
+ Thaqhata, kukinaka, ukatx utjir ajllinaka
+
+
+ Pantjat imaña, justupach phuqhaña
+
+
+
+
+ %1$s ukxata, yanapa
+
+
+ Llikan utjirinaka
+
+
+ Lurañanak turkaña
+
+
+ Taqpacha
+
+
+ Utjirinak apthapita ukatx apnaqaña
+
+ Taqhaña
+
+
+ Pantjata
+
+
+ Aqtayata
+
+
+ jiwt\'ayata
+
+
+ URL justupach phuqhachata
+
+
+ URL ukar wakt\'ayasiñ yapt\'aña
+
+
+ URL ukar wakt\'ayasiñ yapt\'aña
+
+
+ URL Wakt\'ayasiñ yapt\'aña
+
+
+ juk\'a yatiyäwimp llikan utjirinakampi
+
+
+ Utjirinak ajlliña
+
+
+ URLs wakicht\'atanak jittayaña
+
+
+ Juk\'amp amuyañataki
+
+
+ URLs ukar yapkatam ukat justupach wakicht\'añar ucht\'am
+
+
+ URL ukar yapkataña
+
+
+ URL uk ucht\'am jan ukax mantaña
+
+
+ Sakiñani: mozilla.org
+
+
+ Sakiñani: ukhama.com
+
+
+ Machaq wakt\'ayasiña URL yapkatatawa.
+
+
+ Jittayaña
+
+
+ Jittayaña
+
+
+ Pay kut uka URL uk uchktas ukar limt\'am.
+
+ Aru
+
+ Llikaw jan walt\'kiti
+
+ Maynitakiki
+ Yatiyawinak uñachayir jark\'antaña
+ Yaqhip yatiyanakax uñstaskakiwa, janis ukar limt\'kat ukhaxa
+ Uñakipir thaqhirinak jark\'antaña
+ Uka apthapiña, uñakipiri ukhamarak tupur luratanak llamt\'as saraqas apnaqaña
+ Jaqit thaqhirinak jark\'antaña
+ Uka llika janjan k\'iñantatanak mantatanakam thaqtaña ukatx wutunanakar uñacht\'ayaña
+ Yaqha utjir thaqhirinak jark\'antaña
+ Jark\'antasinx inas yaqhip llika janjanakax jan walixchispati
+ Yatiyanak jark\'antaña
+
+ "Yatiyawinakx chikatan chikatapak 3 jist\'antaña"
+
+
+ Amuyump unxtaña
+
+ Llika janjanak imantaña, kunawsatix wakichanakax rukatak ukha
+
+ Luraña
+ Llikan qillqanakap jark\'antaña
+
+ Inas uka chimpus jamuqas jan uñstxchispati
+
+ JavaScript Jark\'antaña
+
+ Janja llikanakax jank\'akiw phuqhachasispa, inas jan ukhamarakikchispati
+
+
+ %1$s justupach thaqhiri
+
+ Musilla
+ Apnaqat imatanak apayaña
+
+
+ Juk’amp yatiña
+
+
+ "Musillax ajllsuwa kunatix askiki ukak churi ukhamarak taqinitakis sumaptayi %1$s"
+
+
+ Maynitakik yatiya
+
+
+ Kunxatasa %1$s
+
+
+ Thaqhirinak uchaña
+
+
+ Jan walt’ir thaqhirinak kutt’ayaniña
+
+
+ Yaqha thakhir yapkataña
+ Thakhirinak jittayaña
+ Jittayaña
+
+
+ Thaqhir yapkataña
+
+ Thaqhirin sutip thaqtaña
+ Apnaqat arunak thaqtaña
+ Imaña
+
+
+ Ukhama: example.com/search/?q=%s
+
+ Machaq thaqhiriw yapkatata
+
+ Thaqhirin sutip ucham
+ Apaqat thaqhiriw pach sutin utji
+
+ Thaqhir arunak ucham
+
+ Thaqhir arunakax purapt’añapawa, uñakipt’am
+
+
+ Uchat pichsum
+
+
+ Apanukuña
+
+
+ Thaqhatanak pichsum
+
+
+ P’uyunak jist’araña: %1$s
+
+
+ Suma waythapiriwa
+
+
+ Phuqhachaskiwa
+
+
+ Phuqhachat llika janjanaka
+
+
+ Juk’ampinaka
+
+
+ Nayraqatar tuyuskakim
+
+
+ Mayamp mantam uka llika janjaru
+
+
+ Qhipar kutt’am
+
+
+ Llika janjanak phuqhachir sayt’ayaña
+
+
+ Nayrir wakichañar kutt’aña
+
+
+ Thaqhirinak jark’antaña
+
+ Yäqayasiñanakama
+
+ Chhitthap yaqha wakichar jist’aram
+
+ Jaytxasmawa %1$s uka chhitthap jist\'arañataki %2$s
+
+ Mä lurañ thaqtam chhitthap jist\'arañapataki
+
+ "Janiw kuna lurawis uka chhitthapx jawsañaman jist\'arkaspati. Jaytxakim %1$s ukhamat yaqha %2$s lurir jist\'arañapataki."
+
+ Uka maynitakik thaqhañat misxatati?
+
+
+ %1$s tukutawa
+
+
+ Jist\'araña
+
+
+
+
+
+
+
+
+
+ Tunux janiw katutakiti
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ jumankirikis uk ch\'amañcham
+
+ Uka juman thaqhatam yaqhar apam. Yatiyäwinak jark\'antam ukat yaqha utjirinak llika janjanakan jan jayarst\'añatal thaqtasmawa.
+
+
+ Thaqhatam ukhamark jumankiri
+
+ Yaqht thaqhaskta? Yaqha thaqhañamp thaqtam.
+
+
+ Jank\'ak mantañanak uka mantañawjar ucht\'asim.
+
+ Sapa kut mantat llikarjank\'ak %1$s kutt\'am. \"Mantañawjar yapkatam\" jiskis uka ajllt\'asin %1$s ucht\'asim.
+
+
+ Uka jumatakikis ukx sapakut ucht\'asim
+
+ %1$s uka thaqhirinakx justupach mistsur ucht\'asim ukat sum aka tuqin jist\'arañanak tuqinakat jikxatam.
+
+ Ukhamax, amuyataxiwa.
+ T\'isktam
+ Ukxaru
+
+
+ -
+
+
+ Yapt\'aña
+
+
+ Sayt\'aña
+
+
+ Jumatakik thaqhañawja
+
+
+ Uka yatiyaw tuqix pichsuñ yanapt\'iristam%1$s limt\'asaki. Janiw uka wakichaw jist\'arañamakiti ukhamarak yaqhatuq thaqtasasa.
+
+
+ Uka thaqhatanak pichsum
+
+
+ Firefox apaqaña
+
+
+
+
+
+
+
+ Suti (qillqayasita)
+ Chimpu
+ Pichsuña
+
+
+
+ Makhatax walikiskiwa
+ Makhatax janiw walikiti
+
+ Uñakipatawa: %1$s
+
+
+ LLika janjanakan sumt\'añapataki
+ URL ukax utjxiwa
+
+
+ Llika janjan jikxatasma
+
+
+ Llika janjan jikxatam
+
+
+ "%1$d /%2$d "
+
+ %1$d mistsutawa %2$d
+
+
+ Ukxarukix uk jikxatam
+
+ Nayrir katjat jikxatam
+
+ Kutt\'añ thaqhañanxa
+
+
+
+
+ Qhanankix uka llik mayiña
+
+
+ URL waruqataxiwa
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-az/strings.xml b/mobile/android/focus-android/app/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000000..aac42062d6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-az/strings.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+ Ləğv et
+ Tamam
+
+ Saxla
+
+ Ünvanı daxil et və ya axtar
+
+ Avtomatik məxfi səyahət.\nGəz. Poz. Təkrarla.
+
+ Səyahət tarixçəniz silindi.
+
+ Vərəqin tarixçəsi pozuldu.
+
+ %1$s üçün axtar
+
+ Paylaş…
+
+ Sayt Problemini Bildir
+
+ %1$s ilə aç
+
+ Bununla aç…
+
+ Ana Ekrana əlavə et
+
+ Tənzimləmələr
+ Haqqında
+ Kömək
+ Hüquqlarınız
+
+ İzləyicilər bloklandı
+
+ Bunu söndürmə bəzi sayt uyğunsuzluqlarını həll edə bilər
+
+ Məzmun Əngəlləmə
+ Bəzi saytlardakı problemləri həll etmək üçün söndürün
+
+ %1$s Tərəfindən
+
+ Bununla paylaş:
+
+ Səyahət keçmişini poz
+
+ Aç
+
+ Poz və Aç
+
+ Poz
+
+ Səyahət keçmişini poz
+
+
+ Poz və Aç
+
+ Poz və %1$s tətbiqini aç
+
+ Məxfilik və Təhlükəsizlik
+
+ İzlənmə, çərəzlər, məlumat seçimləri
+
+ Əsas olaraq qur, avto tamamlama
+
+ %1$s haqqında, kömək
+
+ Web Məzmun
+
+ Tətbiq Keçidi
+
+ Ümumi
+
+ Məlumat yığılması və istifadəsi
+
+ Axtar
+
+ Axtarış təkliflərini al
+ %1$s ünvan sətrində yazdıqlarınızı axtarış mühərriyinizə göndərəcək
+
+ İlkin hal
+
+ Axtarış mühərriyi
+
+ Açıq
+
+ Bağlı
+
+ Ünvan Avtomatik Tamamlama
+
+ Çox istifadə edilən saytlar üçün
+
+ Əlavə etdiyiniz saytlar üçün
+
+ Saytları idarə et
+
+ Saytları idarə et
+
+ + Fərdi ünvan əlavə et
+
+ Fərdi ünvan əlavə et
+
+ Fərdi ünvan əlavə et
+
+ Avtomatik tamamlama üçün keçid əlavə edin
+
+ Çərəzlər və Sayt Məlumatları
+
+ Məlumat Seçimləri
+
+ Fərdi ünvanı sil
+
+ Ətraflı öyrən
+
+ Avtomatik tamamlanacaq fərdi ünvanları idarə edin.
+
+ Əlavə ediləcək ünvan
+
+ Ünvanı yapışdırın və ya daxil edin
+
+ Məsələn: mozilla.org
+
+ Məsələn: example.com
+
+ Yeni fərdi ünvan əlavə edildi.
+
+ Sil
+
+ Sil
+
+ Daxil etdiyiniz ünvanı bir də yoxlayın.
+
+ Dil
+ Sistemin dili
+
+ Məxfilik
+ Reklam izləyicilərini əngəllə
+ Bəzi reklamlar onlara klikləməsəniz belə sayt ziyarətlərinizi izləyirlər
+ Analiz izləyicilərini əngəllə
+ Toxunma və sürüşdürmə kimi aktivlikləri yığıb, ölçüb, analiz etmək üçün işlədilir
+ Sosial izləyiciləri əngəllə
+ Ziyarətinizi izləmək və paylaşma düymələri kimi funksionallıqları göstərmək üçün saytlara yerləşdirilir
+ Digər məzmun izləyicilərini əngəllə
+ Aktivləşdirmə bəzi səhifələrdə gözlənilməz səhvlərə səbəb ola bilər
+ Çərəzləri əngəllə
+
+ Ancaq 3cü tərəf izləmə çərəzlərini əngəllə
+ Ancaq 3cü tərəf çərəzləri əngəllə
+
+ Tətbiqi açmaq üçün barmaq izi işlət
+
+ Görünməzlik
+ Tətbiqlər arası keçərkən saytları gizlət və ekran görüntüləri çəkməyi əngəllə.
+
+ Təhlükəsizlik
+
+ Məhsuldarlıq
+ Web şriftlərini əngəllə
+ Əksik icon və şəkillərə səbəb ola bilər
+
+ JavaScript-i əngəllə
+ Səhifələr daha sürətli yüklənə bilər, ancaq səhv işləyə bilərlər
+
+ %1$s səyyahını əsas et
+
+ Mozilla
+ İstifadə məlumatlarını göndər
+
+ Ətraflı öyrən
+
+ Mozilla ancaq %1$s səyyahını hamı üçün yaxşılaşdırmağa lazım olan məlumatları yığır.
+
+ Məxfilik Bildirişi
+
+ %1$s Haqqında
+
+ Quraşdırılmış axtarış mühərrikləri
+
+ İlkin axtarış nizamlarını bərpa et
+
+ + Digər axtarış mühərriyini əlavə et
+ Axtarış mühərriklərini sil
+ Sil
+
+ Axtarış mühərriyini əlavə et
+
+ Axtarış mühərriyinin adı
+ İstifadə ediləcək axtarış sətri
+ Saxla
+
+ Məsələn: mozillaz.org/search/?q=%s
+
+ Yeni axtarış mühərriyi əlavə edildi.
+
+ Axtarış mühərriyinin adını daxil edin
+ Qurulu axtarış mühərriyi artıq bu adı işlədir.
+
+ Axtarış mətnini daxil edin
+
+ Axtarış mətninin nümunədəki formata uyğun olduğunu yoxlayın
+
+ Girişi təmizlə
+
+ Ləğv et
+
+ Səyahət tarixçəsini təmizlə
+
+ Açıq vərəqlər: %1$s
+
+ Təhlükəsiz əlaqə
+
+ Yüklənir
+
+ Sayt yükləndi
+
+ Digər seçimlər
+
+ Daha çox seçim düyməsi
+
+ İrəli keç
+
+ Saytı yenilə
+
+ Geri get
+
+ Səhifəni yükləməyi dayandır
+
+ Əvvəlki tətbiqə qayıt
+
+ Əngəllənmiş izləyicilərin sayı
+
+ İzləyiciləri əngəllə
+
+ Hüquqlarınız
+
+ Keçidi başqa tətbiqdə aç
+ %1$s səyyahını tərk edərək keçidi %2$s üzərində aça bilərsiz.
+ Keçid aça bilən tətbiq tap
+ Cihazınızdakı heç bir tətbiq bu keçidi aça bilmir. %1$s səyyahını tərk edərək %2$s ilə bunu bacaran tətbiq axtara bilərsiz.
+ Məxfi Səyahətdən çıxırsın?
+
+ %1$s endirildi
+
+ Aç
+
+
+
+
+
+
+
+
+
+ Server tapılmadı
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Məxfiliyinizi gücləndirin
+ Məxfi səyahəti fərqli səviyyəyə götürün. Saytlar üzərindən sizə izləyə biləcək reklam və digər məzmunları bloklayın və yüklənmə sürətini artırın.
+
+ Sizin axtarış, sizin qaydalar
+ Fərqli şeylər axtarırsız? Tənzimləmələrdən fərqli axtarış mühərriyi seçin.
+
+ Ana ekranınıza keçidlər əlavə edin
+ %1$s üzərindəki sevimli saytlarınıza cəld girin. %1$s menyusundan \"Ana ekrana əlavə et\" seçməyiniz kifayətdir.
+
+ Məxfiliyi vərdiş halına gətirin
+ Əsas səyyahınız olaraq %1$s seçin və digər tətbiqlərdən keçid açarkən məxfi səyahətin üstünlüklərindən faydalanın.
+
+ Tamam, başa düşdüm!
+ Keç
+ Sonrakı
+
+ -
+
+ Əlavə et
+
+ Ləğv et
+
+ Məxfi səyahət sessiyası
+
+ Bildirişlər tək toxunuşla %1$s sessiyanınızı pozmağa imkan verəcək. Tətbiqi açmağa və ya səyyahınızda nəyin işlədiyinə baxmağa ehtiyyacınız yoxdur.
+
+ Səyahət keçmişini poz
+
+ Firefox Endir
+
+ %1$s Mozilla və digər töhfəverənlər tərəfindən yaradılmış azad və açıq qaynaqlı proqramdır.
+
+
+
+
+ İstifadəçi adı
+ Parol
+ Təmizlə
+
+ Təhlükəsiz Əlaqə
+ Təhlükəli Əlaqə
+ Doğrulayan: %1$s
+
+ Sayt Təhlükəsizliyi
+ URL artıq mövcuddur
+
+ Səhifədə Tap
+
+ Səhifədə tap
+
+ %1$d/%2$d
+ %2$d nəticədən %1$d dənəsi
+
+ Sonrakı nəticəni tap
+ Əvvəlki nəticəni tap
+ Səhifədə axtarışı qapat
+
+
+
+ Masaüstü saytını istə
+
+ URL köçürüldü
+
+ Tərtibatçı alətləri
+
+ Təkmilləşmiş
+
+ USB/Wi-Fi ilə məsafəli sazlama
+
+ Barmaq izi ikonu
+
+ Barmaq izi tanınmadı. Təkrar yoxlayın.
+
+ Barmaq çox sürətli hərəkət elədi. Təkrar yoxlayın.
+
+ Xeyr
+
+ Bəli
+
+ Bəzi axtarış mühərrikləri məsləhətləri göstərə bilmirlər.
+
+ Qapat
+
+
+
+ Sayt gözlənilməyən hərəkətlər edir?\n İzləmə Qorumasınını söndürməyi yoxlayın
+
+ Çox işlətdiyiniz saytları tək toxunuşla açın%1$s Menyu > Ana Ekrana əlavə et
+
+ Bütün keçidləri %1$s ilə açın\n %1$s əsas səyyahınız olsun
+
+ Çox işlətdiyiniz saytlar üçün avto doldurma\n Ünvan sətrindəki hər hansı bir ünvana uzun basın
+
+ Keçidi yeni vərəqdə açın\n Səhifədə hər hansı bir keçidə uzun basın
+
+ Ana ekranındakı məsləhətləri söndür
+
+ Yeni vərəq açıldı
+
+ Keç
+
+ Yeni vərəqdəki keçidə dərhal keç
+
+ Potensial təhlükəli və aldadıcı saytları əngəllə
+ Zərərverici, aldadıcı, istənməyən saytları əngəllə.
+
+ İstisnalar
+ Bu saytlar üçün Məzmun Əngəlləməni söndürmüsünüz.
+ Sil
+ Bütün saytları sil
+
+ Vərəq Çökdü
+ Üzr istəyirik. Bu vərəqlə əlaqədar problemimiz var.
+ Məxfi səyyah olaraq biz heç vaxt vərəqləri saxlamırıq və bu səbəbdən də bu vərəqi bərpa edə bilməyəcik.
+ Vərəqi Qapat
+
+
+
+
+ Çökmə hesabatını Mozillaya göndər
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-be/strings.xml b/mobile/android/focus-android/app/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000000..bd8a4703c8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-be/strings.xml
@@ -0,0 +1,1086 @@
+
+
+
+
+
+
+
+
+ Скасаваць
+
+ OK
+
+ Захаваць
+
+
+ Шукаць ці ўвесці адрас
+
+ Аўтаматычны прыватнае агляданне.\Паглядзець. Сцерці. Паўтарыць.
+
+
+ Ваша гісторыя прагляду была сцёрта.
+ Гісторыя аглядання выдалена
+
+
+ Гісторыя прагляду карткі была сцёрта.
+
+
+ Шукаць %1$s
+
+
+ Падзяліцца…
+
+
+ Паведаміць аб праблеме з сайтам
+
+
+ Адкрыць у %1$s
+
+
+ Адкрыць у…
+
+
+ Дадаць на хатні экран
+
+
+ Дадаць у цэтлікі
+
+ Выдаліць з цэтлікаў
+
+
+ Налады
+
+
+ Пра нас
+ Даведка
+ Вашы правы
+
+
+ Трэкераў заблакіравана
+
+
+ Адключэнне гэтай функцыі можа вырашыць некаторыя праблемы з сайтам
+
+
+ Блакіроўка змесціва
+
+
+ Адключыць для вырашэння праблем з некаторымі сайтамі
+
+
+ Пры падтрымцы %1$s
+
+
+ Падзяліцца праз
+
+ Сцерці гісторыю аглядання?
+ Націсніце або ачысціце гэта апавяшчэнне, каб надзейна сцерці гісторыю аглядання.
+
+
+ Націсніце або пасуньце гэта апавяшчэнне, каб надзейна сцерці гісторыю аглядання.
+
+ Сцерці гісторыю аглядання
+
+
+ Адкрыць
+
+
+ Сцерці і адкрыць
+
+
+ Сцерці
+
+
+
+ Сцерці гісторыю аглядання
+
+
+
+ Сцерці і адкрыць
+
+
+ Сцерці і адкрыць %1$s
+
+
+ Шукаць у Focus
+
+ Шукаць у Klar
+
+ Шукаць у Focus Beta
+
+ Шукаць у Focus Nightly
+
+
+ %1$s дае вам кантроль.
+Выкарыстоўвайце яго як прыватны браўзер:
+
+ Шукайце і аглядайце наўпрост з праграмы
+ Блакіруйце трэкеры (або змяніце налады, каб дазволіць трэкеры)
+ Сцірайце сеанс, каб выдаліць кукі разам з пошукамі і гісторыяй аглядання
+
+
+%1$s створаны Mozilla. Наша місія — спрыяць здароваму, адкрытаму Інтэрнэту.
+Даведацца больш
]]>
+
+
+ Прыватнасць і бяспека
+
+
+ Асочванне, кукі, выбар даных
+
+
+ Зрабіць прадвызначаным, аўтазапаўненне
+
+
+
+
+ Аб %1$s, дапамога
+
+
+ Узмоцненая ахова ад сачэння
+
+
+ Сеціўнае змесціва
+
+
+ Пераключэнне праграм
+
+
+ Агульныя
+
+
+ Прадвызначаны браўзер, мова
+
+
+ Збор і выкарыстанне даных
+
+ Пошук
+
+
+ Атрымліваць пошукавыя прапановы
+
+
+ %1$s будзе адпраўляць правайдару пошуку тэкст, які вы ўводзіце ў адрасны радок
+
+
+ Прадвызначана
+
+
+ Пашукавік
+
+
+ Укл.
+
+
+ Выкл.
+
+
+ Аўтадапаўненне URL
+
+
+ Для папулярных сайтаў
+
+
+ Уключыць аўтадапаўненне ў %s для больш чым 450 папулярных сайтаў у адрасным радку.
+
+
+ Для даданых вамі сайтаў
+
+
+ Уключыце, каб %s аўтаматычна завяршаў URL-адрасы вашых улюбёных сайтаў.
+
+
+ Кіраванне сайтамі
+
+
+ Кіраванне сайтамі
+
+
+ + Дадаць уласны URL-адрас
+
+
+ Ваш спіс аўтадапаўнення:
+
+
+ Дадаць URL
+
+
+ Дадаць уласны URL-адрас
+
+
+ Дадаць уласны URL-адрас
+
+
+ Дадаць спасылку для аўтадапаўнення
+
+
+ Кукі і даныя сайтаў
+
+
+ Выбар даных
+
+
+ Выдаліць уласныя URL-адрасы
+
+
+ Падрабязней
+
+
+ Даданне і кіраванне ўласнымі URL-адрасамі аўтадапаўнення.
+
+
+ URL для дадавання
+
+
+ Устаўце або ўвядзіце URL
+
+
+ Прыклад: mozilla.org
+
+
+ Прыклад: example.com
+
+
+ Новы ўласны URL-адрас дададзены.
+
+
+ Выдаліць
+
+
+ Выдаліць
+
+
+ Уважліва праверце ўведзены URL-адрас.
+
+ Мова
+
+ Прадвызначаная сістэмы
+
+ Прыватнасць
+ Блакіраваць рэкламнае сачэнне
+
+ Некаторая рэклама асочвае наведванне сайтаў, нават калі вы не націскаеце на яе
+ Блакіраваць трэкеры аналітыкі
+ Выкарыстоўваецца для збору, аналізу і вымярэння дзеянняў, напрыклад, дотыку і пракруткі
+ Блакіраваць трэкеры сацыяльных сетак
+ Убудаваны на сайты для сачэння за вашымі наведваннямі і адлюстравання такіх функцый, як кнопка «Падзяліцца»
+ Блакіраваць іншыя трэкеры змесціва
+ Уключэнне можа прывесці да таго, што некаторыя старонкі будуць паводзіць сябе нечакана
+ Блакіраваць кукі
+
+
+ Не, дзякуй
+
+ Блакіраваць толькі староннія кукі асочвання
+ Блакіраваць толькі староннія кукі
+
+ Блакіраваць міжсайтавыя кукі
+ Так, калі ласка
+
+
+ Выкарыстоўвайце адбіткі пальцаў для разблакіравання праграмы
+
+
+ Разблакіроўка з дапамогай адбітка пальца, калі вы дадалі цэтлікі або калі вэб-сайт ужо адкрыты ў %s.
+
+
+ Скрытнасць
+
+
+ Хаваць вэб-старонкі пры пераключэнні праграм і блакіраваць здымкі экрана.
+
+ Бяспека
+
+ Прадукцыйнасць
+ Блакіраваць вэб-шрыфты
+
+ Можа прывесці да адсутнасці значкоў або відарысаў
+
+ Блакіраваць JavaScript
+
+ Старонкі могуць загружацца хутчэй, але пры гэтым могуць няправільна працаваць
+
+
+ Зрабіць %1$s прадвызначаным браўзерам
+
+ Mozilla
+ Адпраўляць даныя аб выкарыстанні
+
+
+ Падрабязней
+
+
+ Mozilla імкнецца збіраць толькі тыя звесткі, якія патрэбны для работы і паляпшэння %1$s для ўсіх.
+
+
+ Паведамленне аб прыватнасці
+
+
+ Звесткі пра ліцэнзію
+
+
+ Бібліятэкі, якімі мы карыстаемся
+
+
+ %s | Бібліятэкі OSS
+
+
+ Пра %1$s
+
+
+ Усталяваныя пошукавыя сістэмы
+
+
+ Выберыце пошукавую сістэму
+
+
+ Аднавіць прадвызначаныя пошукавыя сістэмы
+
+
+ + Дадаць іншую пошукавую сістэму
+ Выдаліць пошукавыя сістэмы
+ Выдаліць
+
+
+ Дадаць іншую пошукавую сістэму
+
+ Выберыце сваю пошукавую сістэму:
+
+
+ Дадаць пошукавую сістэму
+
+ Назва пошукавай сістэмы
+ Пошукавы радок
+ Захаваць
+
+
+ Прыклад: example.com/search/?q=%s
+
+ Дададзена новая пошукавая сістэма.
+
+ Увядзіце назву пошукавай сістэмы
+
+ Гэта назва ўжо выкарыстоўваецца ва ўсталяванай пошукавай сістэме.
+
+ Увядзіце радок пошуку
+
+ Пераканайцеся, што пошукавы запыт адпавядае Ўзорнаму фармату
+
+
+ Ачысціць увод
+
+
+ Адхіліць
+
+
+ Сцерці гісторыю аглядання
+
+
+ Адкрытых картак: %1$s
+
+
+ Бяспечнае злучэнне
+
+
+ Загрузка
+
+
+ Вэб-сайт загружаны
+
+
+ Дадаткова
+
+
+ Кнопка дадатковых налад
+
+
+ Перайсці наперад
+
+
+ Перазагрузіць сайт
+
+
+ Перайсці назад
+
+
+ Спыніць загрузку сайта
+
+
+ Вярнуцца ў папярэднюю праграму
+
+
+ Колькасць заблакаваных трэкераў
+
+
+ Блакіраваць трэкеры
+
+ Вашы правы
+
+ Адкрыць спасылку ў іншай праграме
+
+
+ Вы можаце выйсці з %1$s, каб адкрыць гэтую спасылку ў %2$s.
+
+ Знайдзіце праграму, якая можа адкрыць спасылку
+
+ Ні адна з праграм на вашай прыладзе не можа адкрыць гэту спасылку. Вы можаце выйсці з %1$s для пошуку ў %2$s праграмы, якая можа.
+
+ Пакінуць Прыватнае агляданне?
+
+
+ %1$s скончана
+
+
+ Адкрыць
+
+
+ Дададзена ў цэтлікі!
+
+ Сервер не знойдзены
+
+
+ Закрыць
+
+
+
+ Вітаем у %1$s
+
+
+ Хуткі. Прыватны. Засяроджаны.
+
+
+ Пачаць
+
+
+
+ %1$s не падобны на іншыя браўзеры
+
+
+ Для павышэння прыватнасці мы выдаляем вашу гісторыю, калі вы закрываеце праграму.
+
+
+
+ Зрабіце %1$s прадвызначаным браўзерам, каб абараніць вашы даныя пры адкрыцці кожнай спасылцы.
+
+
+ Зрабіць прадвызначаным браўзерам
+
+
+ Прапусціць
+
+
+ Узмацніце сваю прыватнасць
+
+ Перайдзіце на новы ўзровень прыватнага аглядання. Блакіруйце рэкламу і іншы змест, які можа сачыць за вамі на сайтах і запавольваць загрузку старонак.
+
+
+ Шукайце па-свойму
+
+
+ Шукаеце нешта іншае? Выберыце іншы прадвызначаны пашукавік у наладах.
+
+
+ Дадайце цэтлікі на хатні экран
+
+ Хутка вярніцеся да сваіх улюбёных сайтаў у %1$s. Проста выберыце \"Дадаць на хатні экран\" у меню %1$s.
+
+
+ Зрабіце прыватнасць звычкаю
+
+
+ Усталюйце %1$s прадвызначаным браўзерам і атрымлівайце перавагі прыватнага аглядання, калі адкрываеце вэб-старонкі з іншых праграм.
+
+ OK, зразумела!
+ Прапусціць
+ Далей
+
+
+ -
+
+
+ Дадаць
+
+ ТАК
+
+
+ Скасаваць
+
+ НЕ
+
+
+ Цэтлік будзе адкрывацца з адключанай узмоцненай аховай ад сачэння
+
+
+ Сеанс прыватнага аглядання
+
+
+ Апавяшчэнні дазваляюць сцерці сеанс %1$s адным дотыкам. Вам не трэба адкрываць праграму або бачыць, што адкрыта ў вашым браўзеры.
+
+
+ Сцерці гісторыю аглядання
+
+
+ Сцягнуць Firefox
+
+
+
+
+
+ Mozilla Public License і іншых ліцэнзій для адкрытага зыходнага коду.]]>
+
+
+ тут.]]>
+
+
+ ліцэнзіямі.]]>
+
+
+ GNU General Public License v3 і даступныя тут .]]>
+
+
+ Імя карыстальніка
+ Пароль
+ Ачысціць
+
+
+
+ Бяспечнае злучэнне
+ Небяспечнае злучэнне
+
+
+ Пацверджана: %1$s
+
+
+ Бяспека сайта
+ URL ўжо існуе
+
+
+ Знайсці на старонцы
+
+
+ Знайсці на старонцы
+
+
+ %1$d/%2$d
+
+ %1$d з %2$d
+
+
+ Знайсці наступны вынік
+
+ Знайсці папярэдні вынік
+
+ Прыбраць пошук на старонцы
+
+
+ Поўная версія сайта
+
+
+ Версія для камп’ютара
+
+
+ URL скапіраваны
+
+
+ Інструменты распрацоўшчыка
+
+
+ Адкрываць спасылкі ў праграмах
+
+
+ Дадаткова
+
+
+ Дазволы для сайтаў
+
+
+ Памяншэнне колькасці банераў кукі
+
+
+ Уключана
+
+
+ Выключана
+
+
+ Памяншэнне колькасці банераў кукі
+
+
+ Бачце менш банераў, аўтаматычна адхіляючы запыты на кукі, калі гэта магчыма.
+
+ -->
+ Памяншэнне колькасці банераў кукі
+
+
+ УКЛЮЧАНА для гэтага сайта
+
+
+ Сайт зараз не падтрымліваецца
+
+
+ ВЫКЛЮЧАНА для гэтага сайта
+
+
+ Памяншэнне колькасці банераў кукі
+
+
+ ВЫКЛЮЧАНА для гэтага сайта
+
+
+ УКЛЮЧАНА для гэтага сайта
+
+
+ Уключыць памяншэнне колькасці банераў кукі для %1$s?
+
+
+ Выключыць памяншэнне колькасці банераў кукі для %1$s?
+
+
+ %1$s выдаліць файлы кукі гэтага сайта і абновіць старонку. Выдаленне ўсіх файлаў кукі можа прывесці да выхаду з сістэмы або ачышчэння кошыка для пакупак.
+
+
+ %1$s можа паспрабаваць аўтаматычна адхіляць запыты на файлы кукі.
+
+
+ Гэты сайт у цяперашні час не падтрымліваецца функцыяй памяншэння колькасці банераў кукі. Хочаце запытаць нашу каманду прагледзець гэты сайт і дадаць падтрымку ў будучыні?
+
+
+ Скасаваць
+
+
+ Запытаць падтрымку
+
+
+ Запыт на падтрымку сайта адпраўлены.
+
+
+ Запыт на падтрымку сайта адпраўлены.
+
+
+
+ %1$s спрабуе адхіліць запыты кукі, каб выдаліць раздражняльныя банеры кукі.\n\nКіруйце параметрамі банераў кукі ў %2$s.
+
+ налады
+
+
+ Аўтапрайграванне
+
+
+ Каб дазволіць гэта:
+
+
+ 1. Перайдзіце ў налады Android
+
+
+ Дазволы]]>
+
+
+ Перайсці ў налады
+
+
+ %1$s на УКЛ]]>
+
+
+ Камера
+
+
+ Мікрафон
+
+
+ Месцазнаходжанне
+
+
+ Апавяшчэнні
+
+
+ Змесціва пад DRM кантролем
+
+
+ Запытваць дазвол
+
+
+ Заблакіраваны
+
+
+ Дазволена
+
+
+ Заблакіравана Android
+
+
+ Дазволіць гук і відэа
+
+
+ Блакіраваць толькі гук
+
+
+ Рэкамендавана
+
+
+ Блакіраваць гук і відэа
+
+
+ Даследаванні
+
+
+ Firefox можа ўсталёўваць і выконваць даследаванні час ад часу.
+
+
+ Падрабязней
+
+
+ Праграма будзе закрыта, каб прымяніць змены
+
+
+ Выдаліць
+
+
+ Актыўна
+
+
+ Скончана
+
+
+ Аддаленая адладка праз USB/Wi-Fi
+
+
+ Разблакіраваць
+
+
+ Пацвердзіце з дапамогай адбітка пальца
+
+
+ Можаце выкарыстаць адбітак пальца, каб працягнуць цяперашні сеанс праграмы.
+
+
+ Адкрыць спасылку ў новым сеансе
+
+
+ Значок адбітка пальца
+
+
+ Адбітак пальца не распазнаны. Паспрабуйце яшчэ.
+
+
+ Палец рухаўся надта хутка. Паспрабуйце зноў.
+
+
+ Паказваць пошукавыя прапановы?
+
+
+ Для атрымання прапаноў %1$s павінен адпраўляць тое, што вы ўводзіце ў адрасны радок, у пошукавую сістэму.
+
+
+ Не
+
+
+ Так
+
+
+ Некаторыя пошукавыя сістэмы не могуць паказваць прапановы.
+
+
+ Адхіліць
+
+
+
+
+ Сайт паводзіць сябе нечакана?\n
+ Паспрабуйце адключыць ахову ад сачэння
+
+
+ Дадаць на хатні экран]]>
+
+
+ Адкрывайце ўсе спасылкі ў %1$s\n
+ Усталяваць %1$s як прадвызначаны браўзер
+
+
+
+ Аўтадапаўненне URL-адрасоў для сайтаў, якімі найчасцей карыстаецеся\n
+ Доўгае націсканне на любы URL у адрасным радку
+
+
+
+ Адкрыць спасылку ў новай картцы\n
+ Доўгае націсканне на любую спасылку на старонцы
+
+
+
+ Выключыць парады на стартавым экране
+
+
+ Адкрыта новая картка
+
+
+ Пераключыць
+
+
+ Уваход у поўнаэкранны рэжым
+
+
+ Неадкладна пераключыцца на спасылку ў новай картцы
+
+
+ Блакаваць патэнцыйна небяспечныя і падманлівыя сайты
+
+ Блакіраваць сайты, якія падманваюць і нападаюць, сайты са шкодным і непажаданым праграмным забеспячэннем.
+
+ Рэжым «Толькі HTTPS»
+
+
+ Аўтаматычна спрабуе падключацца да сайтаў з выкарыстаннем пратаколу шыфравання HTTPS для павышэння бяспекі.
+
+
+ Выключэнні
+
+ Вы адключылі блакіроўку змесціва для гэтых сайтах.
+
+ Выдаліць
+
+
+ Выдаліць усе вэб-сайты
+
+
+ Блакіраваць кукі
+
+
+ Вы хочаце заблакіраваць кукі?
+
+
+ Збой карткі
+
+ Прабачце. З гэтай карткай узніклі праблемы.
+
+ Як прыватны браўзер, мы ніколі не захоўваем і не можам аднавіць гэту картку.
+
+ Закрыць картку
+
+
+ Адправіць справаздачу аб збоі ў Mozilla
+
+
+
+
+ Заблакіравана трэкераў з %s
+
+ Змесціва
+
+ Рэклама
+
+ Сацыяльныя сеткі
+
+ Аналітыка
+
+ Узмоцненая ахова ад сачэння
+
+ Ахова адключана на гэтым сайце
+
+ Ахова ўключана на гэтым сайце
+
+ Бяспечнае злучэнне
+
+ Небяспечнае злучэнне
+
+
+ Трэкеры і скрыпты, якія трэба заблакіраваць
+
+
+ Вярнуцца
+
+
+
+ Выдаліць
+
+ Перайменаваць
+
+ Перайменаваць
+
+ Назва цэтліка
+
+
+ Захаваныя і абагуленыя відарысы <b>не будуць</b> выдалены, калі вы выдаліце гісторыю %1$s
+
+
+
+ Тэма
+
+ Светлая
+
+ Цёмная
+
+ Паводле рэжыму эканоміі зараду
+
+ Тэма прылады
+
+
+ Гэты сайт не падтрымлівае HTTPS
+
+
+ Падрабязней
+ Змяніць гэта можна, перайшоўшы ў Налады > Прыватнасць і бяспека > Бяспека.]]>
+
+
+ Злучэнне не бяспечнае
+
+
+
+ Калі вы паспяхова злучаліся з гэтым серверам раней, памылка можа быць часовай.
+ ]]>
+
+
+ Хтосьці можа спрабаваць падмяніць гэты сайт, і працягваць можа быць рызыкоўна.
+
+ %1$s не давярае %2$s , таму што выдавец яго сертыфіката нявызначаны, сертыфікат самападпісаны, або сервер не дае спраўных прамежкавых сертыфікатаў.
+ ]]>
+
+
+
+ Закрыць картку
+
+
+
+ Папаўся! Мы не дазволілі гэтаму сайту шпіёніць за вамі. Націсніце на шчыт, каб даведацца, што мы блакіруем.
+
+
+ Закрыць усплывальнае акно
+
+
+
+ Вы абаронены!
+
+ Гэтыя прадвызначаныя налады забяспечваюць надзейную абарону. Але вы лёгка можаце змяніць іх у адпаведнасці з вашымі патрэбамі.
+
+ Адхіліць
+
+
+ Націсніце тут, каб сцерці ўсё — гісторыю, кукі, абсалютна ўсё — і пачаць нанова на новай картцы.
+
+
+
+
+ Закрыць
+
+
+ Віджэт пошуку
+
+
+ Гісторыя аглядання выдалена! 🎉
+
+
+ Пачніце сеанс прыватнага аглядання і мы будзем блакіраваць трэкеры і іншыя непажаданыя элементы.
+
+
+ Мы пакінем вас у рэжыме прыватнага аглядання, але ў наступны раз пачніце хутчэй з дапамогай віджэта %1$s на галоўным экране.
+
+
+ Дадаць віджэт на хатні экран
+
+
+ Віджэт дададзены на хатні экран
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-bg/strings.xml b/mobile/android/focus-android/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000000..3ed7a88a6b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,1103 @@
+
+
+
+
+
+
+
+
+ Отказ
+
+ Добре
+
+ Запазване
+
+
+ Търсене или въвеждане на адрес
+
+ Винаги поверително.\nЧетене. Изчистване. Повтаряне.
+
+
+ Историята на разглеждане е изчистена
+ Историята на разглеждане е изчистена
+
+
+ Историята на разглеждане в раздела е изчистена.
+
+
+ Търсене на %1$s
+
+
+ Споделяне…
+
+
+ Докладване на проблем със сайта
+
+
+ Отваряне в/във %1$s
+
+
+ Отваряне в…
+
+
+ Добавяне към екрана
+
+
+ Добавяне на пряк път
+
+ Премахване на пряк път
+
+
+ Настройки
+ Относно
+ Помощ
+ Вашите права
+
+
+ Спрени проследявания
+
+
+ Изключването на тази функция би могла да реши някои проблеми със сайта
+
+
+ Блокиране на съдържанието
+
+ Изключете, за да поправите някои сайтове
+
+
+ С подкрепата на %1$s
+
+
+ Споделяне чрез
+
+ Изчистване историята на разглеждането?
+ Докоснете или изчистете това известие, за да изтриете сигурно историята на сърфирането.
+
+
+ За да премахнете историята от разглеждането докоснете или плъзнете това известие.
+
+ Изчистване историята на разглеждането
+
+
+ Отваряне
+
+
+ Изчистване и отваряне
+
+
+ Изчистване
+
+
+ Изчистване историята на разглеждането
+
+
+
+ Изчистване и отваряне
+
+
+ Изчистване и отваряне на %1$s
+
+
+
+ Търсене с Focus
+
+ Търсене с Klar
+
+ Търсене с Focus Beta
+
+ Търсене с Focus Nightly
+
+
+ %1$s ви дава контрол.
+Използвайте го, за да разглеждате поверително:
+
+ Търсене и разглеждане изцяло в приложението
+ Спиране на проследяването (или позволяване с промяна в настройките)
+ Изчистване на бисквитките, търсенията и историята на разглеждане
+
+
+%1$s се разработва от Mozilla. Нашата мисия е да развиваме здрав и отворен Интернет.
+Научете повече
]]>
+
+
+ Поверителност и защита
+
+
+ Проследяване, бисквитки, избор на данни
+
+
+ Задаване като стандартна, автоматично довършване
+
+
+
+
+ Относно %1$s, помощ
+
+
+ Подобрена защита от проследяване
+
+
+ Съдържание от Мрежата
+
+
+ Превключване на приложения
+
+
+ Основни
+
+
+ Четец по подразбиране, език
+
+
+ Събиране на данни и начини на употреба
+
+ Търсене
+
+
+ Получаване на предложения за търсене
+
+ %1$s ще изпраща въвежданото от вас в адресната лента към търсачката
+
+
+ Използвана търсеща машина
+
+
+ Търсачка
+
+
+ Включено
+
+
+ Изключено
+
+
+ Автоматично довършване на адреси
+
+
+ За често посещаваните страници
+
+
+ Включете, за да имате в адресната лента на %s автоматично довършване на повече от 450 популярни адреси.
+
+
+ За сайтовете, които сте добавили
+
+
+ Включете, за да имате автоматично довършване в %s на любимите адреси.
+
+
+ Управление на сайтове
+
+
+ Управление на сайтове
+
+
+ + Добавяне на потребителски адрес
+
+
+ Вашият списък за автоматично довършване:
+
+
+ Добавяне на адрес
+
+
+ Добавяне на адрес
+
+
+ Добавяне на потребителски адрес
+
+
+ Включване на адреса за автоматичното довършване
+
+
+ Бисквитки и данни на страници
+
+
+ Избор на данни
+
+
+ Премахване на адреси
+
+
+ Научете повече
+
+
+ Добавяне и управление на автоматичното довършване на потребителски адреси.
+
+
+ Адрес за добавяне
+
+
+ Поставете или въведете адрес
+
+
+ Пример: mozilla.org
+
+
+ Пример: example.com
+
+
+ Добавен е нов потребителски адрес.
+
+
+ Премахване
+
+
+ Премахване
+
+
+ Проверете пак адреса, който въведохте.
+
+ Език
+
+ Стандартният за системата
+
+ Поверителност
+ Спиране на следене от реклами
+ Някои реклами проследяват посещенията, даже и да не натискате върху тях
+ Спиране на следене с цел анализ
+ Използват го, за да събират, анализират и измерват дейности като докосване и плъзгане
+ Спиране на следене от социални мрежи
+ Вградени в сайтове, за да ви проследява и да показва функционални елементи, като например бутони за споделяне
+ Спиране на следене през друго съдържание
+ Активирането може да предизвика неочаквано поведение на някои страници
+ Забраняване на бисквитки
+
+
+ Не, благодаря
+ Блокиране на бисквитките само от трети страни
+ забраняване от трети страни
+ Забраняване на бисквитки от трети страни
+ Да, моля
+
+
+ Използване на пръстов отпечатък за отключване на приложението
+
+
+ Отключете чрез пръстов отпечатък, ако сте добавили преки пътища или когато уебсайт вече е отворен в %s.
+
+
+ Невидим
+
+ Скриване на страниците при превключване между приложения и блокиране на правенето на снимки на екрана.
+
+ Защита
+
+ Производителност
+ Спиране на уеб шрифтовете
+
+ Може да доведе до липсващи икони или изображения
+
+ Забраняване на JavaScript
+
+ Страниците зареждат по-бързо, но може да се държат непредсказуемо
+
+
+ Избиране на %1$s като стандартен четец
+
+ Mozilla
+ Изпращане данни за използването
+
+
+ Научете повече
+
+
+ Mozilla се старае да събира само нужните данни за подобряване на %1$s.
+
+
+ Политика на поверителност
+
+
+ Лицензна информация
+
+
+ Използвани библиотеки
+
+
+ %s | библиотеки с отворен код
+
+
+ Относно %1$s
+
+
+ Инсталирани търсачки
+
+
+ Избиране на търсеща машина
+
+
+ Възстановяване на стандартните търсачки
+
+
+ +Добавяне на друга търсачка
+ Премахване на търсачките
+ Премахване
+
+
+ Добавяне на друга търсачка
+
+
+ Изберете предпочитана търсачка:
+
+
+ Добавяне на търсачка
+
+ Име на търсачката
+ Низ за търсене, който да се използва
+ Запис
+
+
+ Пример: example.com/search/?q=%s
+
+ Добавена е нова търсачка.
+
+ Въведете име на търсачка
+ Инсталирана търсеща машина вече използва това име.
+
+ Въведете низ за търсене
+
+ Проверете дали низът за търсене отговаря на формата в примера
+
+
+ Изчистване на полето
+
+
+ Затваряне
+
+
+ Изчистване историята на разглеждането
+
+
+ Отворени раздели: %1$s
+
+
+ Защитена връзка
+
+
+ Зареждане
+
+
+ Страницата е заредена
+
+
+ Повече настройки
+
+
+ Бутон за още опции
+
+
+ Придвижване напред
+
+
+ Презареждане на страницата
+
+
+ Връщане назад
+
+
+ Спиране зареждането на страницата
+
+
+ Връщане към предишното приложение
+
+
+ Брой спрени проследяващи елементи
+
+
+ Спиране на следенето
+
+ Вашите права
+
+ Отваряне на препратка в приложение
+
+ Може да напуснете %1$s, за да отворите препратката в %2$s.
+
+ Търсене на приложение за отваряне на препратката
+
+ Никое от приложенията на устройството ви са способни да отворят тази препратка. Може да напуснете %1$s, за да потърсите в %2$s подходящо приложение.
+
+ Излизане от поверително разглеждане?
+
+
+ %1$s е изтеглен
+
+
+ Отваряне
+
+
+
+
+
+
+
+
+
+
+ Добавено към преки пътища!
+
+ Сървърът не е намерен
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Затваряне
+
+
+
+ Добре дошли при %1$s
+
+
+ Бързо. Поверително. Без разсейване.
+
+
+ Въведение
+
+
+
+ %1$s не е като другите
+
+
+ За допълнителна поверителност премахваме историята при затваряне на приложението.
+
+
+
+ Направете %1$s мрежов четец по подразбиране, за да защитите данните си.
+
+
+ Четец по подразбиране
+
+
+ Пропускане
+
+
+ Усилете вашата поверителност
+
+ Преминете към следващото ниво на поверителност. Спирайте рекламите и друго съдържание, което следи движението ви през интернет и забавя зареждането на страниците.
+
+
+ Вашето търсене, по вашия начин
+
+ Търсите нещо различно? Изберете друга търсеща машина в Настройки.
+
+
+ Добавяне на преки пътища към началния екран
+
+ С %1$s можете бързо да се връщате към най-използваните от вас сайтове. Просто изберете „Добавяне към екрана“ от менюто на %1$s.
+
+
+ Изградете навик за поверителност
+
+ Задайте %1$s като стандартен четец и се възползвайте от поверителността при отваряне на препратки към страници от други приложения.
+
+ Разбрано!
+ Пропускане
+ Напред
+
+
+ –
+
+
+ Добавяне
+
+ ДА
+
+
+ Отказ
+
+ НЕ
+
+
+ Преките пътища ще се отварят с изключена защита от проследяване
+
+
+ Сесия на поверително разглеждане
+
+
+ Уведомленията ви позволяват с едно натискане да изтриете сесията на %1$s. Няма нужда да отваряте приложението или да виждате какво разглеждате в момента.
+
+
+ Изчистване историята на разглеждането
+
+
+ Изтегляне на Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License и други лицензи за отворен код.]]>
+
+
+ тук.]]>
+
+
+ лицензи – безплатни и с отворен код.]]>
+
+
+ GNU General Public License v3, също достъпен тук .]]>
+
+
+ Потребителско име
+ Парола
+ Изчистване
+
+
+
+ Защитена връзка
+ Незащитена връзка
+
+ Проверено от: %1$s
+
+
+ Защита на страницата
+ Адресът вече съществува
+
+
+ Търсене в страницата
+
+
+ Търсене в страницата
+
+
+ %1$d/%2$d
+
+ %1$d от %2$d
+
+
+ Отиване на следващото съвпадение
+
+ Отиване на предишното съвпадение
+
+ Затваряне на търсенето
+
+
+
+
+ Настолна версия на сайта
+
+
+ Настолна версия
+
+
+ Адресът е копиран
+
+
+ Развойни инструменти
+
+
+ Отваряне на препратки в приложения
+
+
+ Разширени
+
+
+ Права на страниците
+
+
+ Намаляване на банерите за бисквитки
+
+
+ Включено
+
+
+ Изключено
+
+
+ Намаляване на банерите за бисквитки
+
+
+ Срещайте по-малко банери за бисквитките чрез автоматичното отхвърляне на въпросите, когато е възможно.
+
+ -->
+ Намаляване на банерите за бисквитки
+
+
+ ВКЛЮЧЕНО за страницата
+
+
+ Страницата не се поддържа
+
+
+ ИЗКЛЮЧЕНО за страницата
+
+
+ Намаляване на банерите за бисквитки
+
+
+ ИЗКЛЮЧЕНО за страницата
+
+
+ ВКЛЮЧЕНО за страницата
+
+
+ Включване на намаляването на банерите за бисквитки за %1$s?
+
+
+ Изключване на намаляването на банерите за бисквитки за %1$s?
+
+
+ %1$s ще изчисти бисквитките на този сайт и ще презареди страницата. Изчистването на всички бисквитки може да ви отпише от системата или да изпразни количките ви за пазаруване.
+
+
+ %1$s може автоматично да опита да отхвърли заявките за бисквитки.
+
+
+ Този сайт в момента не поддържа намаляването на банери за бисквитки. Бихте ли искали нашия екип да прегледа този уебсайт и да добави поддръжка в бъдеще?
+
+
+ Отказ
+
+
+ Заявка за поддръжка
+
+
+ Заявката за поддръжка на сайта е изпратена.
+
+
+ Заявката за поддръжка на сайта е изпратена.
+
+
+
+ %1$s ще опита да откаже заявките за бисквитки, за да не показва досадните запитвания за бисквитките.\n\nПроменете това в %2$s.
+
+
+ настройките
+
+
+ Автоматично възпроизвеждане
+
+
+ За да разрешите:
+
+
+ 1. Отворете Настройки на Android
+
+
+ Разрешения]]>
+
+
+ Към настройките
+
+
+ %1$s в положение ВКЛЮЧЕНО]]>
+
+
+ Камера
+
+
+ Микрофон
+
+
+ Местоположение
+
+
+ Известия
+
+
+ Съдържание под DRM
+
+
+ Винаги да пита
+
+
+ Забраняване
+
+
+ Разрешаване
+
+
+ Забранено от Android
+
+
+ Разрешаване на звук и видео
+
+
+ Блокиране само на аудио
+
+
+ Препоръчани
+
+
+ Без звук и видео
+
+
+ Проучвания
+
+
+ Firefox може, от време на време, да инсталира и извършва проучвания.
+
+
+ Научете повече
+
+
+ Приложението ще се затвори, за да приложи промените
+
+
+ Премахване
+
+
+ Включен
+
+
+ Завършено
+
+
+ Отдалечено отстраняване на дефекти през USB/Wi-Fi
+
+
+ Отключване
+
+
+ Потвърдете с вашия пръстов отпечатък
+
+
+ Вече можете да използвате пръстовия си отпечатък, за да продължите текущата си сесия на приложението.
+
+
+ Отваряне на връзката в нова сесия
+
+
+ Картинка на пръстов отпечатък
+
+
+ Пръстовият отпечатък не е разпознат. Опитайте отново.
+
+
+ Пръстът се придвижи твърде бързо. Опитайте пак.
+
+
+ Показване на предложения за търсене?
+
+
+ За да получите предложения, %1$s трябва да изпрати това, което въвеждате в адресната лента до търсачката.
+
+
+ Не
+
+
+ Да
+
+
+ Някои търсачки не могат да показват предложения.
+
+
+ Затваряне
+
+
+
+
+ Сайтът се държи неочаквано?\n Опитайте да изключите защитата от проследяване
+
+
+ Добавяне към екрана]]>
+
+
+ Отваряйте всяка препратка в %1$s\n Задайте %1$s като стандартен четец
+
+
+ Автоматизирано завършвайте адреси на често посещавани сайтове\n Натиснете продължително върху адрес в адресната лента
+
+
+ Отваряйте препратки в нов раздел\n Натиснете продължително върху препратка в страница
+
+
+ Изключване на съветите на началния екран
+
+
+ Отворен е нов раздел
+
+
+ Превключване
+
+
+ Режим на цял екран
+
+
+ Превключване веднага към новия раздел на препратка
+
+
+ Блокиране на потенциално опасни и измамни сайтове
+
+ Блокиране на докладвани сайтове, които са подвеждащи и атакуващи, или със зловреден или нежелан софтуер.
+
+
+ Режим „само HTTPS“
+
+
+ Автоматично опитване за свързка със сайтове, използвайки HTTPS протокола за криптиране за повишена сигурност.
+
+
+ Изключения
+
+ Изключили сте блокиране на съдържанието за следните сайтове.
+
+ Премахване
+
+ Премахване на всички сайтове
+
+
+ Забраняване на бисквитки
+
+
+ Желаете ли бисквитките да бъдат забранени?
+
+
+ Разделът се срина
+
+ Извинявайте. Случи се проблем с този раздел.
+
+ Като поверителен четец, ние никога не записваме и не можем да възстановим този раздел.
+
+ Затваряне на раздела
+
+
+
+
+
+ Изпращане на доклад за срива на Mozilla
+
+
+
+
+ Спрени проследявания от %s
+
+ Съдържание
+
+ Рекламни
+
+ Социални
+
+ Анализи
+
+ Подобрена защита от проследяване
+
+ Защитите са ИЗКЛЮЧЕНИ за този сайт
+
+ Защитите са ВКЛЮЧЕНИ за този сайт
+
+ Връзката е защитена
+
+ Връзката не е защитена
+
+
+ Проследяващи програми и скриптове за блокиране
+
+
+ Връщане назад
+
+
+
+ Премахване
+
+ Преименуване
+
+ Преименуване
+
+ Име на пряк път
+
+
+ Запазените и споделените изображения <b>няма да бъдат</b> изтрити, когато изтриете историята на %1$s
+
+
+
+ Тема
+
+ Светла
+
+ Тъмна
+
+
+ Зададена от приложение за пестене на батерия
+
+ Като темата на устройството
+
+
+ Този сайт не поддържа HTTPS
+
+
+ Научете повече
+ Променете тази настройка в Настройки > Поверителност & Сигурност > Сигурност.]]>
+
+
+ Връзката не е защитена
+
+
+
+ Ако сте се свързвали успешно с този сървър в миналото, грешката може да е временна.
+ ]]>
+
+
+ Някой може да се опитва да се представя за сайта и продължаването да бъде рисковано.
+
+ %1$s не вярва на %2$s , защото издателят на сертификата му е неизвестен, сертификатът е самоподписан или сървърът не изпраща правилните междинни сертификати.
+ ]]>
+
+
+
+ Затваряне на раздела
+
+
+
+ Хванахме ги! Спряхме този сайт да ви шпионира. Натиснете щита по всяко време, за да видите какво блокираме.
+
+
+ Затваряне на изскачащия прозорец
+
+
+
+ Вие сте защитени!
+
+ Настройките по подразбиране предлагат силна защита. Лесно е да ги промените, за да отговарят на вашите специфични нужди.
+
+ Прекратяване
+
+
+ Натиснете тук за да изхвърлите всичко — история за сърфирането, бисквитки, всичко — и започнете на чисто в нов раздел.
+
+
+
+
+ Затваряне
+
+
+ Приспособление за търсене
+
+
+ Историята на сърфирането е изчистена! 🎉
+
+
+ Започнете личната ви сесия на сърфиране и ние ще блокираме проследяващи програми и други лоши неща, докато го правите.
+
+
+ Ще ви оставим на личното ви сърфиране, но следващия път започнете по-бързо с приспособлението %1$s на началния екран.
+
+
+ Прибавете приспособлението към началния екран
+
+
+ Приспособлението е добавено към началния екран
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-bn/strings.xml b/mobile/android/focus-android/app/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000000..57cbb27250
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-bn/strings.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+ বাতিল
+ ঠিক আছে
+
+ সংরক্ষণ
+
+ অনুসন্ধান করুন বা ঠিকানা লিখুন
+
+ স্বয়ংক্রিয় প্রাইভেন্ট ব্রাউজিং। \nব্রাউজ করুন। মুছে দিন। আবার করুন।
+
+ আপনার ব্রাউজিংয়ের পূর্ববর্তী তথ্য মুছে ফেলা হয়েছে।
+
+ ট্যাব এর ব্রাউজিং ইতিহাস মুছে ফেলা হয়েছে।
+
+ %1$s এর জন্যে অনুসন্ধান করুন
+
+ শেয়ার…
+
+ সাইট ইস্যু রিপোর্ট করুন
+
+ %1$s এ খুলুন
+
+ খুলুন…
+
+ নীড় স্ক্রিনে যোগ
+
+ সেটিং
+ পরিচিতি
+ সহায়তা
+ আপনার অধিকারসমূহ
+
+ ট্র্যাকার ব্লক করা হয়েছে্যা
+
+ বন্ধ করলে, চালু কিছু সাইটের সমস্যা ঠিক হতে পারে
+
+ কন্টেন্ট ব্লকিং
+ কিছু সাইট ফিক্স করা বন্ধ
+
+ %1$s পরিচালিত
+
+ শেয়ারের মাধ্যম
+
+ ব্রাউজিং ইতিহাস মুছে দিন
+
+ খুলুন
+
+ মুছুন এবং খুলুন
+
+ মুছুন
+
+ ব্রাউজিং ইতিহাস মুছুন
+
+
+ মুছে ফেলুন এবং খুলুন
+
+ মুছে ফেলুন এবং %1$s খুলুন
+
+ গোপনীয়তা ও নিরাপত্তা
+
+ ট্র্যাকিং, কুকিজ, লগইন, ডাটা পছন্দ
+
+ ডিফল্ট সেট করুন, অটোকমপ্লিট
+
+ %1$s সম্বন্ধে, সাহায্য
+
+ ওয়েব কনটেন্ট
+
+ সুইচিং অ্যাপস
+
+ সাধারণ
+
+ ডাটা সংগ্রহ ও ব্যবহার
+
+ অনুসন্ধান
+
+ অনুসন্ধান পরামর্শ নিন
+ আপনার অনুসন্ধান ইঞ্জিনে ঠিকানা বারে আপনি যা টাইপ করেন, %1$s তা পাঠাবে
+
+ পূর্বনির্ধারিত
+
+ অনুসন্ধান ইঞ্জিন
+
+ চালু
+
+ বন্ধ
+
+ URL অটোকমপ্লিট
+
+ শীর্ষস্থানীয় সাইটের জন্য
+
+ আপনি যেসব সাইট যোগ করবেন তার জন্যে
+
+ সাইট ব্যবস্থাপনা করুন
+
+ সাইট ব্যবস্থাপনা
+
+ + কাস্টোম URL যোগ করুন
+
+ কাস্টোম URL যোগ করুন
+
+ কাস্টোম URL যোগ করুন
+
+ স্বয়ংপূরণে লিঙ্ক যোগ করুন
+
+ কুকি ও সাইট ডাটা
+
+ ডাটা পছন্দ
+
+ কাস্টোম URL মুছে দিন
+
+ আরও জানুন
+
+ কাস্টোম অটোকমপ্লিট URLs যোগ এবং ম্যানেজ করুন।
+
+ URL যোগ করতে
+
+ URL পেস্ট অথবা প্রবেশ করান
+
+ উদাহরণ: mozilla.org
+
+ উদাহরণ: example.com
+
+ নতুন কাস্টোম URL যোগ করা হয়েছে।
+
+ অপসারণ
+
+ অপসারণ
+
+ যে URL টি আপনি দিলেন তা ডবল চেক করুন।
+
+ ভাষা
+ সিস্টেম ডিফল্ট
+
+ গোপনীয়তা
+ বিজ্ঞাপন গোয়েন্দা আটক করুন
+ কিছু বিজ্ঞাপন, আপনি তাতে ক্লিক না করলেও কি সাইট ব্রাউজ করছেন তা ট্র্যাক করে
+ এনালেটিকস গোয়েন্দা আটক করুন
+ ট্যাপিং বা স্ক্রলিং এর মত কর্মকান্ডের হিসেব রাখতে, বিশ্লেষণ করতে ব্যবহৃত হয়
+ সোশ্যাল নেটওয়ার্ক গোয়েন্দা আটক করুন
+ সাইটে আপনার ব্রাউজ ট্র্যাক করতে এবং শেয়ার বোতামের মত ফাংশনালিটি দেখাতে সাইটে এম্বেড রয়েছে
+ অন্যন্য কনটেন্ট গোয়েন্দা আটক করুন
+ সক্রিয় করলে কিছু পাতা অপ্রত্যাশিত আচরণ করতে পারে
+ কুকি ব্লক কর
+
+ শুধুমাত্র তৃতীয় পক্ষের ট্র্যাকার কুকিজ ব্লক কর
+ "কেবল ৩য়-পক্ষীয় কুকি ব্লক কর "
+
+ অ্যাপ আনলক করার জন্য আঙ্গুলের ছাপ ব্যবহার করুন
+
+ অদৃশ্য
+ অ্যাপ্লিকেশন পরিবর্তনের সময় ওয়েব পেজ লুকান এবং স্ক্রিনশট গ্রহণ ব্লক করুন।
+
+ নিরাপত্তা
+
+ কর্মক্ষমতা
+ ওয়েব ফন্ট ব্লক করুন
+ ছবি অথবা আইকন দৃশ্যমান নাও হতে পারে
+
+ JavaScript ব্লক কর
+ পাতা সম্ভবত দ্রুত লোড হচ্ছে, কিন্তু অনাকাঙ্খিত ভাল দেখাচ্ছে না
+
+ %1$s কে ডিফল্ট ব্রাউজার করুন
+
+ Mozilla
+ ব্যবহারের তথ্য পাঠাও
+
+ আরও জানুন
+
+ Mozilla কেবলমাত্র সংগ্রহ করা চেষ্টা করে যা প্রত্যেকের জন্য %1$s প্রদান এবং উন্নতি করতে হবে।
+
+ গোপনীয়তা নীতি
+
+ %1$s সম্পর্কে
+
+ ইন্সটল করা অনুসন্ধান ইঞ্জিন
+
+ ডিফল্ট অনুসন্ধান ইঞ্জিন পুনরুদ্ধার
+
+ + অন্য অনুসন্ধান ইঞ্জিন যোগ করুন
+ অনুসন্ধান ইঞ্জিন অপসারণ করুন
+ অপসারণ
+
+ অনুসন্ধান ইঞ্জিন যোগ
+
+ অনুসন্ধান ইঞ্জিন নাম
+ ব্যবহারের জন্য অনুসন্ধান বাক্য
+ সংরক্ষণ
+
+ উদাহরণ: example.com/search/?q=%s
+
+ নতুন অনুসন্ধান ইঞ্জিন যোগ হয়েছে।
+
+ অনুসন্ধান ইঞ্জিনের নাম দিন
+ ইনস্টল করা কোন অনুসন্ধান ইঞ্জিন ইতিমধ্যে এই নাম ব্যবহার করছে।
+
+ অনুসন্ধান বাক্য দিন
+
+ সার্চ স্ট্রিংটি যেটি উদাহরণ ফরম্যাটের সাথে মেলে তা চেক করুন
+
+ ইনপুট পরিস্কার
+
+ খারিজ
+
+ ব্রাউজিং ইতিহাস পরিষ্কার করুন
+
+ ট্যাব খোলা: %1$s
+
+ সুরক্ষিত কানেকশন
+
+ লোড হচ্ছে
+
+ ওয়েবসাইট লোড হয়েছে
+
+ আরও বিকল্পসমূহ
+
+ আরো অপশন বাটন
+
+ সামনে পরিভ্রমণ করুন
+
+ ওয়েবসাইট রিলোড করুন
+
+ পেছনে পরিভ্রমণ করুন
+
+ ওয়েবসাইট লোডিং বন্ধ করুন
+
+ আগের অ্যাপে ফিরে যাও
+
+ ব্লক করা ট্র্যাকারের সংখ্যা
+
+ ট্র্যাকার ব্লক করুন
+
+ আপনার অধিকারসমূহ
+
+ নতুন অ্যাপে লিংক খুলুন
+ লিংকটি %2$s এ খুলতে, আপনি %1$s কে ছেড়ে দিতে পারেন।
+ একটি অ্যাপ অনুসন্ধান করো যা লিংখটি খুলতে পারবে
+ আপনার ডিভাইসের কোন অ্যাপই এই লিঙ্কটি খুলতে সক্ষম হয়নি। %2$s অনুসন্ধান করে একটি অ্যাপ বের করার জন্যে আপনি %1$s কে দ্বায়িত্ব দিতে পারেন।
+ আপনি কি ব্যক্তিগত ব্রাউজিং বন্ধ করবেন?
+
+ %1$s সম্পন্ন হয়েছে
+
+ খুলুন
+
+
+
+
+
+
+
+
+
+ সার্ভার পাওয়া যায়নি
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ আপনার গোপনীয়তা কঠোর করুন
+ ব্যাক্তিগত ব্রাউজিং কে আরেক উচ্চতায় নিয়ে যান। বিজ্ঞাপন আর অন্যান্য কনটেন্ট ব্লক করুন, যারা আপনাকে সকল ওয়েবসাইটে অনুসরণ করে বেড়ায় আর আপনার পেজ লোডিং টাইম অনেকগুন বাড়িয়ে দেয়।
+
+ আপনি খুঁজবেন, আমার মত করে
+ অন্য কিছু খুঁজছেন? সেটিংস এ অন্য ডিফল্ট সার্চ ইঞ্জিন পছন্দ করুন।
+
+ নীড় স্ক্রিনে শর্টকাট যোগ করুন
+ %1$s এ আপনার প্রিয় সাইটে দ্রুত ফিরে আসুন। শুধু %1$s মেনু থেকে \"হোম স্ক্রীনে যোগ করুন\" নির্বাচন করুন।
+
+ গোপনীয়তাকে অভ্যাসে পরিনত করুন
+ আপনার ডিফল্ট ব্রাউজার হিসাবে %1$s সেট করুন এবং অন্য অ্যাপ্লিকেশন থেকে ওয়েবপেজ খুললে আপনি ব্যক্তিগত ব্রাউজিং এর সুবিধা পাবেন।
+
+ ঠিক আছে, বুঝতে পেরেছি!
+ উপেক্ষা
+ পরবর্তী
+
+ -
+
+ যোগ
+
+ বাতিল
+
+ ব্যক্তিগত ব্রাউজিং সেশন
+
+ নোাটফিকেশন ট্যাপ করে আপনার %1$s সেশন মুছে ফেলা যাবে। আপনাকে অ্যাপ্লিকেশন খুলতে বা ব্রাউজারে কি চলছে তা দেখার প্রয়োজন হবে না।
+
+ ব্রাউজিং ইতিহাস পরিষ্কার করুন
+
+ Firefox ডাউনলোড করুন
+
+ %1$s হল Mozilla এবং অন্যান্য অবদানকারীর দ্বারা তৈরি ফ্রি এবং ওপেনসোর্স সফটওয়্যার।
+
+
+
+
+ ব্যবহারকারীর নাম
+ পাসওয়ার্ড
+ পরিষ্কার
+
+ নিরাপদ সংযোগ
+ অনিরাপদ সংযোগ
+ যাচাইকারী: %1$s
+
+ সাইট নিরাপত্তা
+ URL ইতোমধ্যেই আছে
+
+ পাতায় অনুসন্ধান
+
+ পাতায় অনুসন্ধান
+
+ %1$d/%2$d
+ %2$d এর %1$d
+
+ পরবর্তী ফলাফল খুঁজুন
+ পূর্ববর্তী ফলাফল খুঁজুন
+ পাতায় খোঁজা বাতিল করুন
+
+
+
+ ডেস্কটপ সাইটের অনুরোধ
+
+ URL কপি হয়েছে
+
+ ডেভেলপার টুল
+
+ বিস্তৃত
+
+ ইউএসবি/ওয়াইফাই এর মাধ্যমে রিমোট ডিবাগিং
+
+ আঙ্গুলের ছাপ আইকন
+
+ আঙ্গুলের ছাপ চেনা যাচ্ছে না। আবার চেষ্টা করুন।
+
+ আঙ্গুল খুব দ্রুত সরে গেছে। আবার চেষ্টা করুন।
+
+ না
+
+ হ্যাঁ
+
+ কিছু সার্চ ইঞ্জিন পরামর্শ প্রদর্শন করতে পারবে না।
+
+ বাতিল
+
+
+
+ সাইট অপ্রত্যাশিত আচরণ করছে?\n ট্র্যাকিং সুরক্ষা বন্ধ করে দেখুন
+
+ আপনার দ্বারা অধিক ব্যবহার হওয়া সাইটসমূহে এক ট্যাপে চলে যান %1$s মেনু > হোম স্ক্রিনে যোগ করুন
+
+ %1$s এর মধ্যে প্রতিটি লিঙ্ক খুলুন\n ডিফল্ট ব্রাউজার হিসাবে %1$s সেট করুন
+
+ আপনার সবচে বেশী ব্যবহৃত স্বয়ংপূরণ URLs\n অ্যাড্রেস বারে যেকোনো URL লং-প্রেস করুন
+
+ একটি নতুন ট্যাব একটি লিঙ্ক খুলুন\n কোন পেজে কোন লিংক লং-প্রেস করুন
+
+ স্টার্ট স্ক্রিনে টিপস বন্ধ করুন
+
+ নতুন ট্যাব খোলা হয়েছে
+
+ পরিবর্তন
+
+ অবিলম্বে নতুন ট্যাব লিঙ্ক স্যুইচ করুন
+
+ সম্ভাব্য বিপজ্জনক এবং প্রতারণামূলক সাইট ব্লক করুন
+ প্রতারণামূলক এবং আক্রমণ সাইট, ম্যালওয়্যার সাইট, এবং অবাঞ্ছিত সফ্টওয়্যারের সাইট ব্লক করুন।
+
+ ব্যতিক্রম
+ আপনি এই ওয়েবসাইটগুলির জন্য সামগ্রী অবরোধ নিষ্ক্রিয় করেছেন।
+ অপসারণ করুন
+ সব ওয়েবসাইট অপসারণ করুন
+
+ ট্যাব ক্র্যাশ হয়েছে
+ দুঃখিত। আমদের এই ট্যাবের সাথে একটি সমস্যা হচ্ছে।
+ ব্যক্তিগত ব্রাউজার হিসাবে, আমরা কখনও সংরক্ষণ এবং এই ট্যাব পুনরুদ্ধার করতে পারব না।
+ ট্যাব বন্ধ করুন
+
+
+
+
+ Mozilla কে ক্র্যাশ রিপোর্ট পাঠান
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-bo/strings.xml b/mobile/android/focus-android/app/src/main/res/values-bo/strings.xml
new file mode 100644
index 0000000000..ebe67860e2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-bo/strings.xml
@@ -0,0 +1,328 @@
+
+
+
+
+
+
+
+ འདོར་བ།
+ ཆོག
+
+ ཉར་གསོག
+
+ འཚོལ་བཤེར་ཡང་ན་དྲ་ཚིཊ་ཁ་བྱང་འདིར་འཇུག་རོཊ།
+
+ རང་འགུལ་སྒེར་དོན་གསང་རྒྱ། དྲྭ་འཚོལ། བསུབ་པ། བསྐྱར་འཚོལ།
+
+ ཁྱེད་ཀྱི་འཚོལ་རབས་འདོར་བསུབས་བྱས་ཟིན།
+
+ རེའུ་མིག་བཟོ་རྟགས་ཀྱི་འཚོལ་རབས་འདོར་བསུབས་བྱས་ཟིན།
+
+ འཚོལ་བྱའི་ནང་དོན་ %1$s
+
+ མཉམ་སྤྱོད་་་
+
+ དྲ་ཚིགས་གནད་དོན་སྙན་སེང་ཞུ་བ།
+
+ ཁ་འབྱེད་ཡུལ་ %1$s
+
+ ཁ་འབྱེད་ཡུལ་་་་
+
+ ཁ་པའི་མདུན་ངོས་ཞོག་ཅིག
+
+ སྒྲིག་འགོད།
+ ངོ་སྤྲོད།
+ རོགས་རམ།
+ ཁྱེད་ཀྱི་བདག་དབང་།
+
+ རྗེས་འདེད་མཁན་བཀག་འགོག
+
+ འདི་གསོད་ན་དྲ་ཚིགས་འགའ་ཤས་ཀྱི་གནད་དོན་བཅོས་སྒྲིག་བྱ་སྲིད།
+
+ ནང་དོན་འགོག་པ།
+
+ མཉམ་སྤྱོད་བརྒྱུད་ལམ།
+
+ འཚོལ་རབས་འདོར་སུབས་བྱོས།
+
+ ཁ་འབྱེད།
+
+ འདོར་སུབ་དང་འཆར་བ།
+
+ སུབ་པ།
+
+ ཁྱོད་ཀྱི་འཚོལ་རབས་འདོར་སུབ་བྱས་ཟིན།
+
+
+ སུབ་པ་དང་ཁ་འབྱེད་པ།
+
+ སུབ་ནས་%1$sཁ་འབྱེད་པ།
+
+ གསང་དོན་དང་བདེ་འཇགས།
+
+ "རྗེས་འདེད། cookies གཞི་གྲངས་འདེམས་པ།"
+
+ རྩ་བ་བསྐོ་སྒྲིག གནད་ཚིག་རང་གྲུབ།
+
+ %1$s ངོ་སྤྲོད། རོགས་རམ།
+
+ དྲ་ངོས་བརྗོད་བྱ།
+
+ མཉེན་ཆས་རྗེ་བཞིན་པ།
+
+ སྤྱི་བཀོལ།
+
+ གནས་ཚུལ་བསྡུས་རུབ་དང་བེད་སྤྱོད།
+
+ འཚོལ་བཤེར།
+
+ འཚོལ་བཤེར་ནང་དོན་འོས་སྦྱོར་ལེན་པ།
+ %1$s ཡིས་ཁྱེད་འཇུག་པའི་འཚོལ་བྱའི་ནང་དོན་འཚོལ་བཤེར་དྲ་བ་གཏོང་ལེན་བྱེད།
+
+ སྔོན་སྒྲིག
+
+ འཚོལ་བཤེར།
+
+ སྤྱོད།
+
+ སྤྱོད་མཚམས་འཇོག
+
+ URL རང་སྒྲུབ།
+
+ དྲ་ཚིགས་གཙུག་ཕོད་ཆེད་དུ།
+
+ ཁྱོད་ཀྱིས་སྣོན་ཟིན་པའི་དྲ་ཚིགས།
+
+ དྲ་ཚིགས་དོ་དམ།
+
+ དྲ་ཚིགས་དོ་དམ།
+
+ "+ རང་བཅོས་དྲ་ཚིཊ་ཁ་བྱང་སྣོན།"
+
+ རང་བཅོས་དྲ་ཚིཊ་ཁ་བྱང་སྣོན།
+
+ རང་བཅོས་དྲ་ཚིཊ་ཁ་བྱང་སྣོན།
+
+ སྦྲེལ་མཐུད་རང་སྒྲུབ་ལ་སྣོན་པ།
+
+ གནས་ཚུལ་འདམ་ག།
+
+ རང་བཅོས་དྲ་ཚིཊ་ཁ་བྱང་སྤོ་འབུད།
+
+ དེ་བས་མང་།
+
+ རང་བཅོས་དྲ་ཚིཊ་ཁ་བྱང་སྣོན་པ་དང་སྟང་འཛིན་བྱེད་པ།
+
+ སྣོན་བྱའི་དྲ་ཚིཊ་ཁ་བྱང་།
+
+ དྲ་ཚིཊ་ཁ་བྱང་འདིར་འཇུག་རོཊ།
+
+ དཔེ་མཚོན། mozilla.org
+
+ དཔེ་མཚོན། example.com
+
+ རང་བཅོས་དྲ་ཚིཊ་ཁ་བྱང་གསར་པ་སྣོན་ཟིན།
+
+ སྤོ་འབུད།
+
+ སྤོ་འབུད།
+
+ ཁྱོད་ཀྱི་འཇུག་པའི་དྲ་ཚིཊ་ཁ་བྱང་དོ་དམ་བྱོས།
+
+ སྐད་ཡིག
+ རྒྱུད་ཁོངས་སྔོན་སྒྲིག
+
+ གསང་དོན།
+ རྗེས་འདེད་གཏོང་མཁན་བཀག་ཟིན་པ།
+
+ མཛུབ་ཐེལ་བེད་སྤྱོད་བྱས་ཏེ་མཉེན་ཆས་ཁ་འབྱེད།
+
+ གསང་ཐབས།
+
+ ཉེན་སྲུང་།
+
+ བྱེད་ལས།
+ དྲ་བའི་ཡིག་གཟུགས་བཀག་པ།
+ འདྲ་གཟུགས་ཡང་ན་པར་རིས་མ་ཚང་བའི་དྲ་ཚིགས་ཐོན་སྲིད་པ།
+
+ JavaScript བཀག་པ།
+ དྲ་ངོས་རྒྱབ་སྣོན་མགྱོགས་ཀྱང་རྒྱུན་ལྡན་མ་ཡིན་པའི་གནས་ཚུལ་ཐོན་སྲིད།
+
+ " %1$s སྔོན་སྒྲིག་བཤར་ཆས་སུ་བཀོད་པ།"
+
+ Mozilla
+
+ དེ་བས་མང་།
+
+ གསང་དོན་གསལ་བརྡ།
+
+ %1$s ངོ་སྤྲོད།
+
+ འཚོལ་བཤེར་སྒྲིག་འཇུག
+
+ སྔོན་སྒྲིག་འཚོལ་བཤེར་རང་ལོག
+
+ + འཚོལ་བཤེར་དྲ་ངོས་གཞན་སྣོན།
+ འཚོལ་བཤེར་སྤོ་འབུད།།
+ སྤོ་འབུད།
+
+ འཚོལ་བཤེར་དྲ་ངོས་སྣོན།
+
+ འཚོལ་བཤེར་དྲ་བའི་མིང་།
+ ཉར་གསོག
+
+ "དཔེར་ན། example.com/search/?q=%s"
+
+ འཚོལ་བཤེར་དྲ་ངོས་གསར་པ་སྣོན་ཟིན།
+
+ འཚོལ་བཤེར་དྲ་ངོས་མིང་འདིར་འཇུག་རོགས།
+ འཚོལ་བཤེར་སྒྲིག་འཇུག་ཟིན་པའི་མིང་རེད་འདུག
+
+ གནད་ཚིག་འཇུག་རོགས།
+
+ ནང་འཇུག་གཙང་བཟོ།
+
+ ཡང་བསྐྱར་མི་འཆར།
+
+ འཚོལ་རབས་འདོར་སུབ་བྱོས།
+
+ བདེ་འཇགས་འབྲེལ་བཐུད།
+
+ རྒྱབ་སྣོན་བྱེད་བཞིན་ཡོད།
+
+ དྲ་ཚིགས་འཇུག་ཟིན།
+
+ གདམ་ཚན་དེ་བས་མང་།
+
+ གདམ་ཚན་མཐེབ་བཅུས་དེ་བས་མང་།
+
+ མདུན་ཕྱོགས་སྒྱུར།
+
+ དྲ་ཚིགས་བསྐྱར་འཇུག
+
+ རྒྱབ་ཕྱོགས་སྒྱུར།
+
+ དྲ་ཚིགས་འཇུག་མཚམས་འཇོག
+
+ མཉེན་ཆས་སྔོན་མ་ཕྱིར་ལོག་བྱོས།
+
+ རྗེས་འདེད་མཁན་བཀག་གྲངས།
+
+ རྗེས་འདེད་མཁན་འགོག
+
+ ཁྱེད་ཀྱི་བདག་དབང་།
+
+ གསང་རྒྱ་ནས་ཕྱིར་ཐོན་དགོས་སམ།
+
+ %1$s ཟིན་པ།
+
+ ཁ་འབྱེད།
+
+
+
+
+
+
+
+
+ ཞབས་ཞུ་འཕྲུལ་ཆས་མ་རྙེད།
+
+
+
+
+
+
+
+
+
+
+
+ སྒེར་དབང་ཆེ་རུ་གཏོང་།
+ སྒེར་རྒྱ་རིམ་པ་རྗེས་མ་འཁྱེར་བ། ཁྱབ་བསྒྲགས་དང་རྗེས་འདེད་དེ་བཞིན་དྲ་ངོས་སྣོན་འགོག་གནད་ཆས་བཀག་འགོག་བྱེད།
+
+ ཁྱོད་ཀྱི་འཚོལ་བཤེར། ཁྱོད་ཀྱི་ལམ།
+ འཚོལ་བྱའི་ནང་དོན་གཞན་དག་ཡིན་ནམ། འཚོལ་བཤེར་གཞན་ཞིག་སྒྲིག་འགོད་ནས་ངོས་གཙོར་འཇོག་རོགས།
+
+ མྱུར་ལམ་བརྙན་ཡོལ་གཙོ་བོ་ཐོག་འཇོག
+
+ གསང་དོན་ཁྱོད་ཀྱི་གོམས་གཤིས་བཟོས།
+ %1$s སྔོན་སྒྲིག་བཤར་ཆས་སུ་བཀོད་དེ་གསང་རྒྱ་ཡི་ཁེ་ཕན་ཡོད་ཚད་དྲ་ཚིགས་མཉེན་ཆས་གཞན་ནས་ཁ་འབྱེད་སྐབས་ལེན་པ།
+
+ ཆོག་ ཤེས་སོང་།
+ མཆོང་པ།
+ རྗེས་མ།
+
+ -
+
+ སྣོན།
+
+ འདོར་བ།
+
+ སྒེར་དོན་གསང་རྒྱ་དུས་མཚམས།
+
+ འཚོལ་རབས་འདོར་སུབ་བྱོས།
+
+ Firefox ཕབ་ལེན།
+
+ སྤྱོད་མཁན་གྱི་མིང་།
+ གསང་གྲངས།
+ གཙང་བཟོ།
+
+ བདེ་འཇགས་འབྲེལ་བཐུད།
+ བདེ་འཇགས་འབྲེལ་བཐུད་མེད་པ།
+ ར་སྤྲོད་མཁན། %1$s
+
+ དྲ་བའི་བདེ་འཇགས།
+ དྲ་ཚིཊ་ཁ་བྱང་སྔོན་ནས་འདུག།
+
+ ཤོག་ངོས་ནང་འཚོལ་བཤེར་བྱོས།
+
+ ཤོག་ངོས་ནང་འཚོལ་བཤེར་བྱོས།
+
+ %1$d/%2$d
+ "%1$d ནང་ནས་ %1$d "
+
+ ཤོག་ངོས་ཞོལ་མ།
+ ཤོག་ངོས་གོང་མ།
+
+
+
+ ཅོག་ངོས་དྲ་ཚིགས་རེ་ཞུ་བྱེད།
+
+ དྲ་ཚིཊ་ཁ་བྱང་བཤུས་ཟིན།
+
+ གསར་སྤེལ་ལག་ཆ།
+
+ མཐོ་རིམ།
+
+ མཛུབ་ཐེལ་འདྲ་གཟུགས།
+
+ མཛུབ་ཐེལ་ངོས་འཛིན་མ་ཐུབ། བསྐྱར་དུ་ཚོད་ལྟ་བྱོས།
+
+ མཛུག་མོ་འགུལ་སྐྱོད་མགྱོགས་དྲག་འདུག་ བསྐྱར་དུ་ཚོད་ལྟ་བྱོས།
+
+ མིན།
+
+ རེད།
+
+ བཤེར་ཆས་འགའ་ཤས་ཀྱིས་འཚོལ་བཤེར་འབྲེལ་ཡོད་འོས་སྦྱོར་མ་ཐུབ།
+
+ འདོར་བ།
+
+
+
+ དྲ་ཚིགས་རྒྱུན་གཏན་ལྟར་མི་འདུག་གམ།\n རྗེས་འདེད་སྲུང་སྐྱོབ་གསོད་དེ་བསྐྱར་ཞིག་བྱོས།
+
+ བེད་སྤྱོད་མང་ཤོས་བྱེད་པའི་དྲ་ཚིཊ་ཁ་བྱང་རད་གྲུབ། དྲ་ཚིཊ་ཁ་བྱང་ཐོག་མཛུབ་མོ་རྒྱུན་རིང་གནོན།
+
+ རྒྱུན་འགལ།
+ སྤོ་འབུད།
+ དྲ་ཚིགས་ཆ་ཚང་སྤོ་འབུད།།
+
+ དགོངས་དག ང་ཚོ་རེའུ་མིག་བཟོ་རྟགས་འདི་དང་གནད་དོན་འཕྲད་སོང་།
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-bs/strings.xml b/mobile/android/focus-android/app/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000000..fe9e8ad22f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-bs/strings.xml
@@ -0,0 +1,1102 @@
+
+
+
+
+
+
+
+
+ Otkaži
+
+ U redu
+
+ Sačuvaj
+
+
+ Tražite ili upišite adresu
+
+ Automatsko privatno pretraživanje.\nPretražujte. Izbrišite. Ponovite.
+
+
+ Vaša historija pretraživanja je izbrisana.
+ Historija pretraživanja je izbrisana
+
+
+ Historija tab pretraživanja je izbrisana.
+
+
+ Traži %1$s
+
+
+ Dijeli…
+
+
+ Prijavite problem sa stranicom
+
+
+ Otvori u %1$s
+
+
+ Otvori u…
+
+
+ Dodaj na početni ekran
+
+
+ Dodaj u prečice
+
+ Ukloni iz prečica
+
+
+ Postavke
+ O aplikaciji
+ Pomoć
+ Vaša prava
+
+
+ Blokirani pratioci
+
+
+ Isključivanje ovog može riješiti neke probleme na web stranici
+
+
+ Blokiranje sadržaja
+
+ Isključi da popravite neke web stranice
+
+
+ Pokreće ga %1$s
+
+
+ Dijeli putem
+
+ Izbrisati historiju pretraživanja?
+ Dodirnite ili izbrišite ovo obavještenje da sigurno izbrišete svoju historiju pretraživanja.
+
+
+ Dodirnite ili povucite ovo obavještenje da sigurno izbrišete svoju historiju pretraživanja.
+
+ Izbrišite historiju pretraživanja
+
+
+ Otvori
+
+
+ Izbriši i otvori
+
+
+ Izbriši
+
+
+ Izbriši historiju pretraživanja
+
+
+
+ Izbriši i otvori
+
+
+ Izbriši i otvori %1$s
+
+
+ Traži u Fokusu
+
+ Traži u Klaru
+
+ Traži u Focus Beta
+
+ Traži u Focus Nightly
+
+
+ %1$s daje ti kontrolu.
+Koristi ga kao privatni pretraživač:
+
+ traži i pretražuj direktno u aplikaciji
+ blokiraj programe za praćenje (ili ih omogući u postavkama)
+ izbriši sve kolačiće kao i historiju traženja i surfanja
+
+
+%1$s je kreirala Mozilla. Naša misija je njegovanje zdravog i otvorenog interneta.
+Saznajte više
]]>
+
+
+ Privatnost i sigurnost
+
+
+ Praćenje, kolačići, izbori podataka
+
+
+ Postavi kao zadano, automatsko dovršavanje
+
+
+
+
+ O %1$s, pomoć
+
+
+ Napredna zaštita od praćenja
+
+
+ Web sadržaj
+
+
+ Mijenjanje aplikacija
+
+
+ Opće
+
+
+ Zadani pretraživač, jezik
+
+
+ Prikupljanje podataka i korištenje
+
+ Traži
+
+
+ Dobijte prijedloge za pretraživanje
+
+ %1$s će poslati ono što unesete u adresnu traku vašem pretraživaču
+
+
+ Izvorno
+
+
+ Pretraživač
+
+
+ Uključeno
+
+
+ Isključeno
+
+
+ Automatsko završavanje URL-a
+
+
+ Za top stranice
+
+
+ Omogućite da %s automatski završi preko 450 popularnih URL adresa u adresnoj traci.
+
+
+ Za stranice koje dodate
+
+
+ Omogućite da %s automatski dovršava vaše omiljene URL-ove.
+
+
+ Upravljaj web stranicama
+
+
+ Upravljaj web stranicama
+
+
+ + Dodaj prilagođeni URL
+
+
+ Vaša lista za automatsko dovršavanje:
+
+
+ Dodaj URL
+
+
+ Dodaj prilagođeni URL
+
+
+ Dodaj prilagođeni URL
+
+
+ Dodaj link za automatsko dovršavanje
+
+
+ Kolačići i podaci stranice
+
+
+ Izbori podataka
+
+
+ Ukloni prilagođene URL-ove
+
+
+ Saznajte više
+
+
+ Dodajte i upravljajte prilagođenim automatski završenim URL-ovima.
+
+
+ URL za dodavanje
+
+
+ Zalijepite ili unesite URL
+
+
+ Primjer: mozilla.org
+
+
+ Primjer: example.com
+
+
+ Novi prilagođeni URL dodan.
+
+
+ Ukloni
+
+
+ Ukloni
+
+
+ Dvaput provjerite URL koji ste unijeli.
+
+ Jezik
+
+ Izvorni sistemski
+
+ Privatnost
+ Blokiraj reklame
+ Neke reklame prate posjete stranica, čak i kad ne klinete na reklame
+ Blokiraj analitičke pratioce
+ Koristi se za sakupljanje, analiziranje i mjerenje aktivnosti kao što su tipkanje i skrolovanje
+ Blokiraj društvene pratioce
+ Ugrađene u stranice kako bi pratile vaše posjete i da prikažu funkcije kao što su dugmići za dijeljenje
+ Blokiraj pratioce ostalog sadržaja
+ Omogućavanje može uticati loše na neke stranice
+ Blokiraj kolačiće
+
+
+ Ne, hvala
+ Blokiraj samo kolačiće za praćenje trećih strana
+ Blokiraj samo kolačiće trećih strana
+
+ Blokiraj kolačiće trećih strana
+ Da, molim
+
+
+ Koristite otisak prsta za otključavanje aplikacije
+
+ Otključajte pomoću otiska prsta ako ste dodali prečice ili kada je web stranica već otvorena u %s.
+
+
+ Tajni način
+
+ Sakrij web stranice prilikom prebacivanja između aplikacija
+
+ Sigurnost
+
+ Performanse
+ Blokiraj web fontove
+
+ Može prouzrokovati nestajanje ikona ili slika
+
+ Blokiraj JavaScript
+
+ Stranice će se možda učitavati brže, ali također se mogu ponašati neočekivano
+
+
+ Učini %1$s glavnim pretraživačem
+
+ Mozilla
+ Šalji podatke o korištenju
+
+
+ Saznajte više
+
+
+ Mozila nastoji prikupljati samo ono što trebamo da osiguramo i poboljšamo %1$s za sve.
+
+
+ Napomena o privatnosti
+
+
+ Informacije o licenciranju
+
+
+ Biblioteke koje koristimo
+
+
+ %s | OSS biblioteke
+
+
+ O %1$s
+
+
+ Instalirani pretraživači
+
+
+ Odaberi pretraživač
+
+
+ Vrati izvorne pretraživače
+
+
+ + Dodaj novi pretraživač
+ Ukloni pretraživače
+ Ukloni
+
+
+ Dodaj još jedan pretraživač
+
+ Odaberite željeni pretraživač:
+
+
+ Dodajte pretraživač
+
+ Ime pretraživača
+ Pretraži tekst za korištenje
+ Sačuvaj
+
+
+ Primjer: example.com/search/?q=%s
+
+ Novi pretraživač dodan.
+
+ Unesite naziv pretraživača
+ Instalirani pretraživač već koristi to ime.
+
+ Unesite string za pretragu
+
+ Provjerite da traženi string odgovara formatu primjera
+
+
+ Očisti unos
+
+
+ Odbaci
+
+
+ Izbriši historiju pretraživanja
+
+
+ Otvorenih tabova: %1$s
+
+
+ Sigurna veza
+
+
+ Učitavanje
+
+
+ Web stranica učitana
+
+
+ Više opcija
+
+
+ Dugme za više opcija
+
+
+ Idite naprijed
+
+
+ Ponovo učitaj web stranicu
+
+
+ Idite nazad
+
+
+ Zaustavi učitavanje web stranice
+
+
+ Povratak na raniju aplikaciju
+
+
+ Broj blokiranih pratilaca
+
+
+ Blokiraj pratioce
+
+ Vaša prava
+
+ Otvori vezu u drugoj aplikaciji
+
+ Možete napustiti %1$s da otvorite ovu vezu u %2$s.
+
+ Pronađite aplikaciju koja može otvoriti vezu
+
+ Nijedna od aplikacija na vašem uređaju nije u mogućnosti otvoriti vezu. Možete napustiti %1$s da pretražite %2$s za aplikaciju koja može.
+
+ Izaći iz privatnog pretraživanja?
+
+
+ %1$s završeno
+
+
+ Otvori
+
+
+
+
+
+
+
+
+
+
+ Dodato u prečice!
+
+ Server nije pronađen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zatvori
+
+
+
+ Dobrodošli u %1$s
+
+
+ Brz. Privatan. Bez ometanja.
+
+
+ Započnite
+
+
+
+ %1$s nije kao drugi pretraživači
+
+
+ Čistimo vašu historiju kada zatvorite aplikaciju radi dodatne privatnosti.
+
+
+
+ Neka %1$s bude zadan da zaštitite svoje podatke sa svakim linkom koji otvorite.
+
+
+ Postavi kao zadani pretraživač
+
+
+ Preskoči
+
+
+ Osnažite vašu privatnost
+
+ Unaprijedite privatno pretraživanje. Blokirajte reklame i drugi sadržaj koji vas mogu pratiti putem stranica i ubrzajte učitavanje stranica.
+
+
+ Tražite, na svoj način
+
+ Tražite nešto drugačije? Izaberite drugi zadani pretraživač u postavkama.
+
+
+ Dodajte prečice na vaš početni zaslon
+
+ Vratite se brzo na vaše omiljene stranice u %1$s. Samo izaberite \"Dodaj na početni zaslon\" iz %1$s menija.
+
+
+ Učinite privatnost navikom
+
+ Postavite %1$s kao vaš glavni pretraživač i dobijte koristi od privatnog pretraživanja kada otvarate web stranice iz drugih aplikacija.
+
+ U redu, razumijem!
+ Preskoči
+ Sljedeće
+
+
+ -
+
+
+ Dodaj
+
+
+ DA
+
+
+ Otkaži
+
+
+ NE
+
+
+ Prečica će se otvoriti sa onemogućenom poboljšanom zaštitom od praćenja
+
+
+ Sesija za privatno pretraživanje
+
+
+ Obaviještenja vam omogućavaju da izbrišete vašu %1$s sesiju jednim dodirom. Ne morate da otvorite aplikaciju ili da provjerite šta se izvršava u vašem pretraživaču.
+
+
+ Izbriši historiju pretraživanja
+
+
+ Preuzmite Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License i drugih licenci otvorenog kôda.]]>
+
+
+ ovdje.]]>
+
+
+ licencama.]]>
+
+
+ GNU General Public License v3, i dostupnima ovdje .]]>
+
+
+ Korisničko ime
+ Lozinka
+ Očisti
+
+
+
+ Sigurna veza
+ Nesigurna veza
+
+ Provjerio: %1$s
+
+
+ Sigurnost stranice
+ URL već postoji
+
+
+ Pronađi na stranici
+
+
+ Pronađi na stranici
+
+
+ %1$d/%2$d
+
+ %1$d od %2$d
+
+
+ Pronađi sljedeći rezultat
+
+ Pronađi prethodni rezultat
+
+ Odbaci pronalazak na stranici
+
+
+
+
+ Zahtjevaj desktop stranicu
+
+
+ Desktop stranica
+
+
+ URL je kopiran
+
+
+ Razvojni alati
+
+
+ Otvori linkove u aplikacijama
+
+
+ Napredno
+
+
+ Dozvole stranica
+
+
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Uključeno
+
+
+ Isključeno
+
+
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Vidite manje banera automatskim odbijanjem zahtjeva za kolačićima, kada je to moguće.
+
+ -->
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Uključeno za ovu stranicu
+
+
+ Stranica trenutno nije podržana
+
+
+ Isključeno za ovu stranicu
+
+
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Isključeno za ovu stranicu
+
+
+ Uključeno za ovu stranicu
+
+
+ Uključiti smanjenje dijaloga kolačića za %1$s?
+
+
+ Isključiti smanjenje dijaloga kolačića za %1$s?
+
+
+ %1$s će očistiti kolačiće ove stranice i osvježiti stranicu. Čišćenje svih kolačića može vas odjaviti ili isprazniti kolica za kupovinu.
+
+
+ %1$s može pokušati automatski odbiti zahtjeve za kolačiće.
+
+
+ Ova stranica trenutno nije podržana od strane Smanjivanja pojavljivanja dijaloga kolačića. Želite li zatražiti od našeg tima da pregleda ovu web stranicu i doda podršku u budućnosti?
+
+
+ Otkaži
+
+
+ Zatražite podršku
+
+
+ Zahtjev za podršku stranice je poslat.
+
+
+ Zahtjev za podršku stranice je poslat.
+
+
+
+ %1$s pokušava odbiti zahtjeve za kolačiće da odbaci dosadne banere za kolačiće.\n\nUpravljajte postavkama banera kolačića u %2$s.
+
+ postavke
+
+
+ Automatska reprodukcija
+
+
+ Dozvoli na sljedeći način:
+
+
+ 1. Idi na Android postavke
+
+
+ Dozvole]]>
+
+
+ Idi na Postavke
+
+
+ %1$s na UKLJUČENO]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Lokacija
+
+
+ Obavještenje
+
+
+ DRM-kontrolisani sadržaj
+
+
+ Pitaj za dozvolu
+
+
+ Blokirano
+
+
+ Dozvoljeno
+
+
+ Blokirao Android
+
+
+ Dozvoli audio i video
+
+
+ Blokiraj samo audio
+
+
+ Preporučeno
+
+
+ Blokiraj audio i video
+
+
+ Studije
+
+
+ Firefox može s vremena na vrijeme instalirati i pokrenuti studije.
+
+
+ Saznajte više
+
+
+ Aplikacija će se zatvoriti radi primjene promjena
+
+
+ Ukloni
+
+
+ Aktivno
+
+
+ Završeno
+
+
+ Daljinsko otklanjanje grešaka putem USB/Wi-Fi veze
+
+
+ Otključaj
+
+
+ Potvrdite korištenjem otiska prsta
+
+
+ Možete koristiti otisak prsta da nastavite trenutnu sesiju aplikacije.
+
+
+ Otvorite link u novoj sesiji
+
+
+ Ikona otiska prsta
+
+
+ Otisak prsta nije prepoznat. Pokušaj ponovo.
+
+
+ Prst se kretao prebrzo. Pokušaj ponovo.
+
+
+ Prikaži prijedloge za pretraživanje?
+
+
+ Da dobijete prijedloge, %1$s treba da pošalje pretraživaču ono što unesete u adresnu traku.
+
+
+ Ne
+
+
+ Da
+
+
+ Neki pretraživači ne mogu prikazati prijedloge.
+
+
+ Odbaci
+
+
+
+
+ Stranica se ponaša neočekivano?\n
+ Pokušajte isključiti zaštitu od praćenja
+
+
+ Dodaj na početni ekran]]>
+
+
+ Otvorite svaki link u %1$su\n
+ Postavite %1$s kao zadani pretraživač
+
+
+
+ Automatsko dovršavanje URL-ova za web stranice koje najčešće koristite\n
+ Dugo pritisnite bilo koji URL u adresnoj traci
+
+
+
+ Otvorite link u novom tabu\n
+ Dugo pritisnite bilo koji link na stranici
+
+
+
+ Isključite savjete na početnom ekranu
+
+
+ Otvoren novi tab
+
+
+ Promijeni
+
+
+ Ulazim u režim cijelog ekrana
+
+
+ Odmah se prebacite na link u novom tabu
+
+
+ Blokirajte potencijalno opasne i varljive stranice
+
+ Blokirajte prijavljene obmanjujuće i napadne stranice, web stranice sa zlonamjernim softverom i web stranice neželjenog softvera.
+
+ Način rada samo za HTTPS
+
+ Automatski pokušava da se poveže na stranice koristeći HTTPS protokol za šifrovanje radi povećane sigurnosti.
+
+
+ Izuzeci
+
+ Onemogućili ste blokiranje sadržaja za ove web stranice.
+
+ Ukloni
+
+ Ukloni sve web stranice
+
+
+ Blokiraj kolačiće
+
+
+ Želite li blokirati kolačiće?
+
+
+ Tab se srušio
+
+ Žao nam je. Imamo problem s ovim tabom.
+
+ Kao privatni pretraživač, nikada ne pohranjujemo i ne možemo vratiti ovaj tab.
+
+ Zatvori tab
+
+
+ Pošalji izvještaj o rušenju Mozilla-i
+
+
+
+
+ Programi za praćenje blokirani od %s
+
+ Sadržaj
+
+ Oglašavanje
+
+ Društvene mreže
+
+ Analitika
+
+ Napredna zaštita od praćenja
+
+ Zaštite za ovu stranicu su ISKLJUČENE
+
+ Zaštite za ovu stranicu su UKLJUČENE
+
+ Veza je sigurna
+
+ Veza nije sigurna
+
+ Pratioci i skripte za blokiranje
+
+
+ Idi nazad
+
+
+
+ Ukloni
+
+ Preimenuj
+
+ Preimenuj
+
+ Naziv prečice
+
+
+ Sačuvane i podijeljene slike <b>neće biti</b> izbrisane kada izbrišete %1$s historiju
+
+
+
+ Tema
+
+ Svijetla
+
+ Tamna
+
+ Postavljeno u postavkama za štednju baterije
+
+ Slijedi temu uređaja
+
+
+ Ova stranica ne podržava HTTPS
+
+
+ Saznajte više
+ Promijenite ovu postavku u Postavke > Privatnost i sigurnost > Sigurnost.]]>
+
+
+ Veza nije sigurna
+
+
+
+ Ako ste se u prošlosti uspješno povezali s ovim serverom, greška može biti privremena.
+ ]]>
+
+
+ Neko se možda pokušava lažno predstavljati za web stranicu i nastavak bi mogao biti rizičan.
+
+ %1$s nema povjerenja u %2$s jer je njegov izdavalac certifikata nepoznat, certifikat je samopotpisan ili server ne šalje ispravne posredničke certifikate.
+ ]]>
+
+
+
+ Zatvori tab
+
+
+
+ Imamo ih! Spriječili smo ovu stranicu da vas špijunira. Dodirnite štit bilo kada da vidite šta blokiramo.
+
+
+ Zatvori iskočni prozor
+
+
+
+ Zaštićeni ste!
+
+
+ Ove zadane postavke nude snažnu zaštitu. Ali lahko je podesiti postavke kako bi zadovoljile vaše specifične potrebe.
+
+ Odbaci
+
+
+ Dodirnite ovdje da sve bacite u smeće — historiju, kolačiće, sve — i počnete ispočetka u novom tabu.
+
+
+
+
+ Zatvori
+
+
+ Programčić za pretraživanje
+
+
+ Historija pretraživanja je izbrisana! 🎉
+
+
+ Započnite svoju privatnu sesiju pretraživanja, a mi ćemo blokirati programe za praćenje i druge loše stvari dok idete.
+
+
+ Prepustit ćemo vas vašem privatnom pretraživanju, ali sljedeći put započnite brže s programčićem %1$s na početnom ekranu.
+
+
+ Dodajte programčić na početni ekran
+
+
+ Programčić je dodat na početni ekran
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ca/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000000..5485257b07
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ca/strings.xml
@@ -0,0 +1,1095 @@
+
+
+
+
+
+
+
+
+ Cancel·la
+
+ D\'acord
+
+ Desa
+
+
+ Escriviu una cerca o adreça
+
+ Navegació privada automàtica.\nNavegueu. Esborreu. Torneu-hi.
+
+
+ S\'ha esborrat l\'historial de navegació.
+ S’ha esborrat l’historial de navegació
+
+
+ S\'ha esborrat l\'historial de navegació de la pestanya.
+
+
+ Cerca %1$s
+
+
+ Comparteix…
+
+
+ Informa d\'un problema amb el lloc
+
+
+ Obre amb el %1$s
+
+
+ Obre amb…
+
+
+ Afegeix a pantalla d\'inici
+
+
+ Afegeix a les dreceres
+
+ Elimina de les dreceres
+
+
+ Paràmetres
+ Quant a
+ Ajuda
+ Els vostres drets
+
+
+ Elements blocats
+
+
+ Si es desactiva, pot ser que s\'arreglin problemes amb alguns llocs
+
+
+ Bloqueig de contingut
+
+ Desactiveu-ho per arreglar problemes amb alguns llocs
+
+
+ Funciona amb el %1$s
+
+
+ Comparteix mitjançant
+
+ Voleu esborrar l\'historial de navegació?
+ Toqueu o esborreu aquesta notificació per a esborrar de manera segura l\'historial de navegació.
+
+ Esborra l\'historial de navegació
+
+
+ Obre
+
+
+ Esborra i obre
+
+
+ Esborra
+
+
+ Esborra l\'historial de navegació
+
+
+
+ Esborra i obre
+
+
+ Esborra i obre el %1$s
+
+
+
+ Cerca en el Focus
+
+ Cerca en el Klar
+
+ Cerca en el Focus Beta
+
+ Cerca en el Focus Nightly
+
+
+ El %1$s us dóna el control.
+Com a navegador privat, us permet:
+
+ Cercar i navegar directament des de l’aplicació
+ Blocar els elements de seguiment (o canviar els paràmetres per permetre’ls)
+ Esborrar les galetes i l’historial de cerques i de navegació
+
+
+El %1$s està creat per Mozilla. La nostra missió és fomentar un Internet obert i saludable.
+Més informació
]]>
+
+
+ Privadesa i seguretat
+
+
+ Seguiment, galetes, elecció de dades
+
+
+ Valor per defecte, emplenament automàtic
+
+
+
+
+ Quant al %1$s, ajuda
+
+
+ Protecció contra el seguiment millorada
+
+
+ Contingut web
+
+
+ Canvi d\'aplicacions
+
+
+ General
+
+
+ Navegador per defecte, llengua
+
+
+ Ús i recollida de dades
+
+ Cerca
+
+
+ Mostra suggeriments de cerca
+
+ El %1$s enviarà al motor de cerca tot allò que escriviu a la barra d\'adreces
+
+
+ Per defecte
+
+
+ Motor de cerca
+
+
+ Activat
+
+
+ Desactivat
+
+
+ Emplenament automàtic d\'URL
+
+
+ Per als llocs principals
+
+
+ Activeu-lo per fer que el %s empleni automàticament més de 450 URL populars a la barra d’adreces.
+
+
+ Per als llocs que afegiu
+
+
+ Activeu-ho per fer que el %s empleni automàticament els vostres URL preferits.
+
+
+ Gestiona els llocs
+
+
+ Gestiona els llocs
+
+
+ + Afegeix un URL personalitzat
+
+
+ La vostra llista d’emplenament automàtic:
+
+
+ Afegeix un URL
+
+
+ URL personalitzat nou
+
+
+ Afegeix URL personalitzat
+
+
+ Afegeix l\'enllaç a l\'emplenament automàtic
+
+
+ Galetes i dades dels llocs
+
+
+ Elecció de dades
+
+
+ Elimina URL personalitzats
+
+
+ Més informació
+
+
+ Afegiu i gestioneu els URL d\'emplenament automàtic personalitzats.
+
+
+ URL per afegir
+
+
+ Enganxeu o escriviu un URL
+
+
+ Exemple: mozilla.org
+
+
+ Exemple: example.com
+
+
+ S\'ha afegit un URL personalitzat nou.
+
+
+ Elimina
+
+
+ Elimina
+
+
+ Reviseu l\'URL que heu introduït.
+
+ Llengua
+
+ Valor per defecte del sistema
+
+ Privadesa
+ Bloca els elements de seguiment de publicitat
+ Alguns anuncis fan el seguiment de les visites als llocs, fins i tot si no feu clic als anuncis
+ Bloca els elements de seguiment d\'anàlisi
+ S\'utilitzen per recollir, analitzar i mesurar activitats com ara els tocs i el desplaçament
+ Bloca els elements de seguiment social
+ Està incrustat en els llocs per fer el seguiment de les vostres visites i per mostrar alguns elements com els botons de compartició
+ Bloca altres elements de seguiment de contingut
+ Si s\'activa, algunes pàgines podrien comportar-se de forma inesperada
+ Bloca les galetes
+
+
+ No, gràcies
+ Bloca només les galetes de seguiment de tercers
+ Bloca només les galetes de tercers
+ Bloca les galetes entre llocs
+ Sí
+
+
+ Utilitza l\'empremta digital per desblocar l\'aplicació
+
+
+ Desbloqueu amb l’empremta digital si heu afegit dreceres o quan un lloc web ja estigui obert en el %s.
+
+
+ Invisible
+
+ Amaga les pàgines web en canviar d\'aplicacions i bloca les captures de pantalles.
+
+ Seguretat
+
+ Rendiment
+ Bloca tipus de lletra web
+
+ Pot fer que no es vegin algunes icones o imatges
+
+ Bloca el JavaScript
+
+ Les pàgines es poden carregar més ràpid, però també poden comportar-se de forma inesperada
+
+
+ Fes que el %1$s sigui el navegador per defecte
+
+ Mozilla
+ Envia dades d\'ús
+
+
+ Més informació
+
+
+ Mozilla s\'esforça per recollir només les dades necessàries per oferir i millorar el %1$s per a tothom.
+
+
+ Avís de privadesa
+
+
+ Informació de la llicència
+
+
+ Biblioteques que utilitzem
+
+
+ %s | Biblioteques de codi obert
+
+
+ Quant al %1$s
+
+
+ Motors de cerca instal·lats
+
+
+ Trieu un motor de cerca
+
+
+ Restaura els motors de cerca per defecte
+
+
+ + Afegeix un altre motor de cerca
+ Elimina motors de cerca
+ Elimina
+
+ Afegeix un altre motor de cerca
+
+ Trieu el motor de cerca que preferiu:
+
+
+ Afegeix un motor de cerca
+
+ Nom del motor de cerca
+ Cadena de cerca que s\'utilitzarà
+ Desa
+
+
+ Exemple: example.com/search/?q=%s
+
+ S\'ha afegit un motor de cerca nou.
+
+ Escriviu el nom del motor de cerca
+ Ja hi ha un motor de cerca instal·lat amb aquest nom.
+
+ Escriviu una cadena de cerca
+
+ Comproveu que la cadena de cerca coincideixi amb el format d\'exemple
+
+
+ Esborra l\'entrada
+
+
+ Descarta
+
+
+ Esborra l\'historial de navegació
+
+
+ Pestanyes obertes: %1$s
+
+
+ Connexió segura
+
+
+ S\'està carregant…
+
+
+ S\'ha carregat el lloc web
+
+
+ Més opcions
+
+
+ Botó de més opcions
+
+
+ Navega cap endavant
+
+
+ Actualitza el lloc web
+
+
+ Vés enrere
+
+
+ Atura la càrrega del lloc web
+
+
+ Torna a l\'aplicació anterior
+
+
+ Nombre d\'elements blocats
+
+
+ Bloca els elements de seguiment
+
+ Els vostres drets
+
+ Obre l\'enllaç en una altra aplicació
+
+ Podeu sortir del %1$s per obrir aquest enllaç amb el %2$s.
+
+ Cerca una aplicació que pugui obrir aquest enllaç
+
+ No hi ha cap aplicació en el dispositiu que pugui obrir aquest enllaç. Podeu sortir del %1$s per cercar al %2$s una aplicació que pugui obrir-lo.
+
+ Voleu sortir de la navegació privada?
+
+
+ %1$s ha acabat
+
+
+ Obre
+
+
+
+
+
+
+
+
+
+
+ S’ha afegit a les dreceres
+
+ No s\'ha trobat el servidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tanca
+
+
+
+ Us donem la benvinguda al %1$s
+
+
+ Ràpid. Privat. Sense distraccions.
+
+
+ Primers passos
+
+
+
+ El %1$s no és com els altres navegadors
+
+
+ Esborrem el vostre historial quan tanqueu l’aplicació per garantir la privadesa.
+
+
+
+ Feu que el %1$s sigui el navegador per defecte i protegiu les vostres dades cada vegada que obriu un enllaç.
+
+
+ Defineix com a navegador per defecte
+
+
+ Omet
+
+
+ Potencieu la privadesa
+
+ Porteu la navegació privada a un altre nivell. Bloqueu anuncis i altre contingut que us fan el seguiment i alenteixen la càrrega de les pàgines.
+
+
+ Cerqueu com vulgueu
+
+ Busqueu una altra cosa? Trieu un altre motor de cerca per defecte als Paràmetres.
+
+
+ Afegiu dreceres a la pantalla d\'inici
+
+ Retorneu ràpidament als vostres llocs preferits amb el %1$s. Trieu «Afegeix a pantalla d\'inici» en el menú del %1$s.
+
+
+ Feu de la privadesa un hàbit
+
+ Definiu el %1$s com a navegador per defecte i gaudiu de navegació privada en obrir pàgines web des d\'altres aplicacions.
+
+ Entesos
+ Omet
+ Següent
+
+
+ -
+
+
+ Afegeix
+
+ SÍ
+
+
+ Cancel·la
+
+ NO
+
+
+ La drecera s’obrirà amb la protecció contra el seguiment millorada desactivada
+
+
+ Sessió de navegació privada
+
+
+ Les notificacions permeten esborrar la sessió del %1$s amb un sol toc. No cal ni obrir l\'aplicació ni mostrar el contingut del navegador.
+
+
+ Esborra l\'historial de navegació
+
+
+ Baixa el Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License (Llicència Pública de Mozilla) i altres llicències lliures.]]>
+
+
+ aquí.]]>
+
+
+ llicències lliures i de codi obert.]]>
+
+
+ Llicència Pública General de GNU v3 i que estan disponibles aquí .]]>
+
+
+ Nom d\'usuari
+ Contrasenya
+ Esborra
+
+
+
+ Connexió segura
+ Connexió insegura
+
+ Verificat per: %1$s
+
+
+ Seguretat del lloc
+ L\'URL ja existeix
+
+
+ Cerca a la pàgina
+
+
+ Cerca a la pàgina
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Cerca el següent resultat
+
+ Cerca el resultat anterior
+
+ Tanca la cerca
+
+
+
+
+ Mostra el lloc d\'escriptori
+
+
+ Lloc d’escriptori
+
+
+ S\'ha copiat l\'URL
+
+
+ Eines per a desenvolupadors
+
+
+ Obre els enllaços en les aplicacions
+
+
+ Avançat
+
+
+ Permisos dels llocs
+
+
+ Reducció de bàners de galetes
+
+
+ Activada
+
+
+ Desactivada
+
+
+ Reducció de bàners de galetes
+
+
+ Vegeu menys bàners rebutjant automàticament les sol·licituds de galetes, quan sigui possible.
+
+ -->
+ Reducció de bàners de galetes
+
+
+ Activada en aquest lloc
+
+
+ Aquest lloc no és compatible ara per ara
+
+
+ Desactivada en aquest lloc
+
+
+ Reducció de bàners de galetes
+
+
+ Desactivada en aquest lloc
+
+
+ Activada en aquest lloc
+
+
+ Voleu activar la reducció de bàners de galetes per a %1$s?
+
+
+ Voleu desactivar la reducció de bàners de galetes per a %1$s?
+
+
+ El %1$s esborrarà les galetes d’aquest lloc i actualitzarà la pàgina. En esborrar totes les galetes, pot ser que se us tanquin les sessions o que se us buidin els carros de la compra.
+
+
+ El %1$s pot intentar rebutjar automàticament les sol·licituds de galetes.
+
+
+ Actualment, aquest lloc web no és compatible amb la Reducció de bàners de galetes. Voleu que el nostre equip ho revisi i el faci compatible en el futur?
+
+
+ Cancel·la
+
+
+ Sol·licita que sigui compatible
+
+
+ S’ha enviat la sol·licitud per a fer que aquest lloc sigui compatible.
+
+
+ S’ha enviat la sol·licitud per a fer que aquest lloc sigui compatible.
+
+
+
+ El %1$s intenta rebutjar les sol·licituds de galetes per tancar els molestos bàners de galetes.\n\nGestioneu les preferències dels bàners de galetes en els %2$s.
+
+ paràmetres
+
+
+ Reproducció automàtica
+
+
+ Per a permetre-ho:
+
+
+ 1. Aneu a la configuració de l’Android
+
+
+ Permisos]]>
+
+
+ Ves als paràmetres
+
+
+ %1$s]]>
+
+
+ Càmera
+
+
+ Micròfon
+
+
+ Ubicació
+
+
+ Notificacions
+
+
+ Contingut controlat per DRM
+
+
+ Demana-m’ho
+
+
+ Blocat
+
+
+ Permès
+
+
+ Blocat per l’Android
+
+
+ Permet àudio i vídeo
+
+
+ Bloca només àudio
+
+
+ Recomanat
+
+
+ Bloca àudio i vídeo
+
+
+ Estudis
+
+
+ El Firefox pot instal·lar i executar estudis de tant en tant.
+
+
+ Més informació
+
+
+ L’aplicació es tancarà per aplicar els canvis
+
+
+ Elimina
+
+
+ Actius
+
+
+ Completats
+
+
+ Depuració remota per USB/Wi-Fi
+
+
+ Desbloca
+
+
+ Confirmeu mitjançant l’empremta digital
+
+
+ Podeu utilitzar l’empremta digital per continuar la sessió actual de l’aplicació.
+
+
+ Obre l’enllaç en una sessió nova
+
+
+ Icona d\'empremta digital
+
+
+ No s\'ha reconegut l\'empremta digital. Torneu-ho a provar.
+
+
+ El dit s\'ha mogut massa ràpid. Torneu-ho a provar.
+
+
+ Voleu que es mostrin suggeriments de cerca?
+
+
+ Per veure els suggeriments, el %1$s ha d’enviar al motor de cerca tot allò que escriviu a la barra d’adreces.
+
+
+ No
+
+
+ Sí
+
+
+ No tots els motors de cerca poden mostrar suggeriments.
+
+
+ Tanca
+
+
+
+
+ El lloc té un comportament inesperat?\n Proveu de desactivar la protecció contra el seguiment
+
+
+ Afegeix a pantalla d\'inici]]>
+
+
+ Obriu tots els enllaços amb el %1$s\n Definiu el %1$s com a navegador per defecte
+
+
+ Emplenament automàtic dels URL dels llocs que més utilitzeu\n Manteniu premut qualsevol URL de la barra d\'adreces
+
+
+ Obriu un enllaç en una pestanya nova\n Manteniu premut qualsevol enllaç de la pàgina
+
+
+ Desactiva els consells de la pantalla d\'inici
+
+
+ S\'ha obert una pestanya nova
+
+
+ Vés-hi
+
+
+ Esteu en mode de pantalla completa
+
+
+ En obrir un enllaç en una pestanya nova, vés-hi immediatament
+
+
+ Bloca els llocs potencialment perillosos i maliciosos
+
+ Bloca els llocs atacants i els llocs amb programari maliciós i indesitjable.
+
+
+ Mode només HTTPS
+
+
+ Intenta connectar-se als llocs mitjançant el protocol de xifratge HTTPS automàticament per millorar la seguretat.
+
+
+ Excepcions
+
+ Heu desactivat el bloqueig de contingut en aquests llocs web.
+
+ Elimina
+
+ Elimina tots els llocs web
+
+
+ Bloca les galetes
+
+
+ Voleu blocar les galetes?
+
+
+ La pestanya ha fallat
+
+ S\'ha produït un problema amb aquesta pestanya.
+
+ Com a navegador privat, aquesta pestanya no s\'ha desat i, per tant, no es pot restaurar.
+
+ Tanca la pestanya
+
+
+
+
+
+ Envia un informe de fallada a Mozilla
+
+
+
+
+ Elements de seguiment blocats des del %s
+
+ Contingut
+
+ Anuncis
+
+ Xarxes socials
+
+ Anàlisis
+
+ Protecció contra el seguiment millorada
+
+ S’han desactivat les proteccions per a aquest lloc
+
+ S’han activat les proteccions per a aquest lloc
+
+ La connexió és segura
+
+ La connexió no és segura
+
+ Elements de seguiment i scripts que es blocaran
+
+
+ Vés enrere
+
+
+
+ Elimina
+
+
+ Reanomena
+
+ Reanomena
+
+ Nom de la drecera
+
+
+ Les imatges que deseu i compartiu <b>no s’eliminaran</b> quan esborreu l’historial del %1$s
+
+
+
+ Tema
+
+ Clar
+
+ Fosc
+
+ Definit per l’estalvi de bateria
+
+ Segons el tema del dispositiu
+
+
+ Aquest lloc no admet HTTPS
+
+
+ Més informació
+ Podeu canviar aquest paràmetre a Paràmetres > Privadesa i seguretat > Seguretat.]]>
+
+
+ La connexió no és segura
+
+
+
+ Si us hi heu connectat sense cap problema alguna altra vegada, l’error podria ser temporal.
+ ]]>
+
+
+ És possible que algú estigui intentant suplantar el lloc i podria ser arriscat continuar.
+
+ El %1$s no confia en %2$s perquè l’emissor del seu certificat és desconegut, el certificat està signat per ell mateix o el servidor no envia els certificats intermedis correctes. ]]>
+
+
+
+ Tanca la pestanya
+
+
+
+ Enxampats! Hem impedit que aquest lloc us espiï. Toqueu l’escut en qualsevol moment per veure què hem blocat.
+
+
+ Tanca la finestra emergent
+
+
+
+ Esteu protegit!
+
+
+ Aquest paràmetre per defecte ofereix una protecció sòlida. Però és fàcil ajustar els paràmetres per satisfer les vostres necessitats específiques.
+
+ Descarta
+
+
+ Toqueu aquí per eliminar-ho tot (historial, galetes, tot!) i tornar a començar en una pestanya nova.
+
+
+
+
+ Tanca
+
+
+ Giny de cerca
+
+
+ S’ha esborrat l’historial de navegació! 🎉
+
+
+ Inicieu la vostra sessió de navegació privada i blocarem els elements de seguiment i altres males herbes a mesura que navegueu.
+
+
+ Us deixem en la vostra navegació privada, però si la pròxima vegada voleu anar més de pressa, afegiu el giny del %1$s a la pantalla d’inici.
+
+
+ Afegeix el giny a la pantalla d’inici
+
+
+ S’ha afegit el giny a la pantalla d’inici
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-cak/strings.xml b/mobile/android/focus-android/app/src/main/res/values-cak/strings.xml
new file mode 100644
index 0000000000..b55f4732d7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-cak/strings.xml
@@ -0,0 +1,1030 @@
+
+
+
+
+
+
+
+
+ Tiq\'at
+
+ ÜTZ
+
+ Tiyak
+
+
+ Tikanöx o ketz\'ib\'äx ri taq ochochib\'äl
+
+ Ruyonil ichinan okem pa k\'amaya\'l.
+
+
+ Ri runatab\'al okem pa k\'amaya\'l xyuj el.
+
+ Josq\'in kinatab\'al okem pa k\'amaya\'l
+
+
+ Xyuj ri ruwi\' runatab\'al okem pa k\'amaya\'l.
+
+
+ Tikanöx %1$s
+
+
+ Tikomonïx…
+
+
+ Tiya\' Rutzijol ri Ruk\'ayewal Ruxaq K\'amaya\'l
+
+
+ Tijaq pa %1$s
+
+
+ Tijaq pa…
+
+
+ Titz\'aqatisäx pa ri Nïm tzuwäch
+
+
+ Titz\'aqatisäx choj okem
+
+ Tiyuj pa taq choj okem
+
+
+ Nuk’ulem
+ Chi rij
+ To\'ïk
+ Taq Ach\'ojib\'al
+
+
+ Eq\'aton ojqanela\'
+
+
+ We nichup re re\' yerusöl jub\'a\' jujun taq ruk\'ayewal ruxaq
+
+
+ Ruq\'atik Rupam
+
+ Tachupu\' richin nachojmirisaj jujun ruxaq
+
+
+ B\'anon ruma %1$s
+
+
+ Tikomonïx b\'ey
+
+ La niyuj runatab\'al okem pa k\'amaya\'l?
+
+ Tiyuj runatab\'al okem pa k\'amaya\'l
+
+
+ Tijaq
+
+
+ Tiyuj chuqa\' Tijaq
+
+
+ Tiyuj
+
+
+ Tiyuj runatab\'al okem pa k\'amaya\'l
+
+
+
+ Tiyuj & tijaq
+
+
+ Tiyuj chuqa\' tijaq %1$s
+
+
+
+ Tikanöx pa Focus
+
+ Tikanöx pa Klar
+
+ Tikanöx pa Focus Beta
+
+ Tikanöx pa Focus Nightly
+
+
+ %1$s nuya\' ronojel pan aq\'a\'.
+Tawokisaj achi\'el jun ichinan okik\'amaya\'l:
+
+ Tikanöx chuqa\' tokisäx ri chokoy
+ Keq\'at ojqanela\' (o kek\'ex taq cha\'oj richin niya\' q\'ij chi ke ri ojqanela\')
+ Tiyuj richin yeyuj el taq kaxlawey chuqa\' runatab\'al kanoxïk chuqa\' okem pa k\'amaya\'l
+
+
+%1$s Xsamajin ruma ri Mozilla. Ri taqel qataqanem ja ri niqayäk qak\'u\'x richin jun jaqäl chuqa\' raxinäq k\'amaya\'l.
+Tetamäx ch\'aqa\' chik
]]>
+
+
+ Ichinanem & Jikomal
+
+
+ Ojqanem, taq kaxlanwäy, kicha\'oj taq tzij
+
+
+ Tijikib\'äx chi k\'o wi, ruyonil titz\'aqatisäx
+
+
+
+
+ Chi rij %1$s, to\'ïk
+
+
+ Utzirisan Chajinïk chuwäch Ojqanem
+
+
+ Ajk\'amaya\'l Rupam
+
+
+ Jaloj chi kikojol Chokoy
+
+
+ Chijun
+
+
+ Okik\'amaya\'l e k\'o wi, ch\'ab\'äl
+
+
+ Kimolik Tzij & Okisaxïk
+
+ Tikanöx
+
+
+ Kechilab\'ëx ri yekanöx
+
+ %1$s xtutäq ri xtatz\'ib\'aj pa ri rukajtz\'ik ochochib\'äl chi re ri akanob\'al
+
+
+ K\'o wi
+
+
+ Rusamajel kanob\'äl
+
+
+ Titzij
+
+
+ Tichup
+
+
+ URL Titz\'aqatisäx ruyon
+
+
+ Kichin Ütz taq ajk\'amaya\'l ruxaq
+
+
+ Titzij richin nik\'ul %1$s ruyonil nunojisaj naqaj chi re 450 ajowan taq URLs pa kikajtz\'ik ochochib\'äl.
+
+
+ Kichin ri taq Ruxaq Ye\'atz\'aqatisaj
+
+
+ Titzij richin chi ri %s nunojisaj pa ruyonil ri taq URLs ye\'awajo\'.
+
+
+ Kenuk\'samajïx taq ruxaq
+
+
+ Kenuk\'samajïx taq ruxaq
+
+
+ + Titz\'aqatisäx ichinan URL
+
+
+ Ri kicholb\'al kiyon enojisan:
+
+
+ Titz\'aqatisäx URL
+
+
+ Titz\'aqatisäx ichinan URL
+
+
+ Titz\'aqatisäx ichinan URL
+
+
+ Titz\'aqatisäx ri ximonel richin ruyon nutz\'aqatisaj
+
+
+ Taq Kaxlanwäy chuqa\' Rutzij K\'amaya\'l
+
+
+ Kicha\'ik taq Tzij
+
+
+ Tiyuj ichinan URLs
+
+
+ Tetamäx ch\'aqa\' chik
+
+
+ Titz\'aqatisäx chuqa\' tinuk\'samajïx ichinan ruyonil yenojisäx taq URLs.
+
+
+ URL richin nitz\'aqatisäx
+
+
+ Titz\'ajb\'äx o titz\'ib\'äx URL
+
+
+ Tz\'eteb\'äl: mozilla.org
+
+
+ Rutz\'etb\'al: example.com
+
+
+ K\'ak\'a\' ichina URL xtz\'aqatisäx.
+
+
+ Tiyuj
+
+
+ Tiyuj
+
+
+ Tajikib\'a\' ri URL xatz\'ib\'aj.
+
+ Ch\'ab\'äl
+
+ Q\'inoj k\'o wi
+
+ Ichinanem
+ Keq\'at ojqanela\'
+ Jujun taq rutzijol taluxik yekojqaj ri kitzetik taq ruxaq k\'amaya\'l, estape\' man napïtz\' ta pa kiwi\'
+ Keq\'at ch\'ob\'onel taq ojqanela\'
+ Ye\'okisäx richin yemol, yech\'ob\' chuqa\' yepaj taq samaj achi\'el ri taq chapoj chuqa\' ri taq q\'axanïk
+ Keq\'at winaqil taq ojqanela\'
+ Yekinïm pa taq ruxaq k\'amaya\'l richin yekojqaj ri taq atz\'etoj chuqa\' yekik\'üt taq samaj achi\'el ri taq pitz\'b\'äl richin nikomonïx
+ Keq\'at ch\'aqa\' chik ojqanela\' rupam
+ We nitzij re\' rik\'in jub\'a\' nub\'än chi jujun taq ruxaq man kan ta ütz yesamäj
+ Keq\'at kaxlanwäy
+
+
+ Mani matyox
+ Keq\'at xa xe 3rx kiwinaqil kikaxlanwäy ojqanela\'
+ Keq\'at xa xe 3rx kiwinaqil kaxlanwäy
+ Keq\'at ronojel Kikuki xoch\'in taq ruxaq
+ Ja\', tab\'ana\' utzil
+
+
+ Tawokisaj retal ruwi\' aq\'a\' richin man niq\'at ta chik ri chokoy
+
+
+ Taq\'ata\' rik\'in ri retal ruwi\' q\'ab\'aj we xatz\'aqatisaj chojokem o toq jun ruxaq ajk\'amaya\'l jaqäl chik pa %s.
+
+
+ Ewan
+
+ Ke\'ewäx ri ajk\'amaya\'l taq ruxaq toq yejal taq chokoy chuqa\' yeq\'at chapoj taq ruwa.
+
+ Jikomal
+
+ Rub\'eyal nisamäj
+ Tiq\'at ruxe\'el Ajk\'amaya\'l
+
+ Tikirel nib\'anatäj pa kiwachaj chuqa\' taq wachib\'äl
+
+ Tiq\'at JavaScript
+
+ Ri taq ruxaq yetikïr yesamäj aninäq, po chuqa\' yetikïr yesamäj rik\'in man etaman ta rub\'anikil
+
+
+ Ticha\' %1$s richin okik\'amaya\'l k\'o wi
+
+ Mozilla
+ Ketaq taq tzij richin ye\'okisäx
+
+
+ Tetamäx ch\'aqa\' chik
+
+
+ Ri Mozilla nutïj ruq\'ij chi numöl ri niqajo\' niqataluj chuqa\' niqutzilaj %1$s k\'atzinel chi qe qonojel.
+
+
+ Rutzijol Ichinanem
+
+
+ Na\'oj chi rij ya\'öl q\'ij
+
+
+ Taq wujb\'äl yeqokisaj
+
+
+ %s | OSS taq wujb\'äl
+
+
+ Chi rij ri %1$s
+
+
+ Kanonela\' eyakon chupam
+
+
+ Ticha\' kanob\'äl
+
+
+ Ketzolïx ri kisamajel kanob\'äl ruk\'amon wi pe
+
+
+ + Titz\'aqatisäx jun chik kanob\'äl
+ Tiyuj kanob\'äl
+ Tiyuj
+
+ Titz\'aqatisäx jun chik kanob\'äl
+
+
+ Tacha\' ri jeb\'ël awokik\'amaya\'l
+
+
+ Titz\'aqatisäx kanob\'äl
+
+ Rub\'i\' ri kanob\'äl
+ Rucholajem kanoxïk richin nokisäx
+ Tiyak
+
+
+ Tz\'etb\'äl: example.com/search/?q=%s
+
+ Xtz\'aqatisäx k\'ak\'a\' kanob\'äl.
+
+ Titz\'ib\'äx ri rub\'i\' ri kanob\'äl
+ Yakon chik jun kanob\'äl rik\'in ri b\'i\'aj ri\'.
+
+ Titz\'ib\'äx rucholajem ri nikanöx
+
+ Tijikib\'äx chi ri rucholajem ri nikanöx nuk\'äm ri\' rik\'in ri rub\'anikil tz\'etb\'äl
+
+
+ Tiyuj okitz\'ib\'
+
+
+ Tewüx
+
+
+ Tiyuj runatab\'al okem pa k\'amaya\'l
+
+
+ Jaqäl taq ruwi\': %1$s
+
+
+ Ütz okem
+
+
+ Tajin nusamajin
+
+
+ Ruxaq ajk\'amaya\'l qasan
+
+
+ Ch\'aqa\' chik taq cha\'oj
+
+
+ Kipitz\'b\'al ch\'aqa\' cha\'oj
+
+
+ Katok pa k\'amaya\'l chuwäch
+
+
+ Tisamajix chik ruxaq ajk\'amaya\'l
+
+
+ Katok pa k\'amaya\'l titzolin
+
+
+ Tiq\'at ruqasaxik ruxaq ajk\'amaya\'l
+
+
+ Titzolin pa ri jun chokoy
+
+
+ Kajilab\'al taq ojqanela\' eq\'aton
+
+
+ Keq\'at ojqanela\'
+
+ Taq Ach\'ojib\'al
+
+ Tijaq ximonel pa jun chik chokoy
+
+ Yatikïr yatel %1$s richin najäq re ximonel re\' pa %2$s.
+
+ Tikanöx jun chokoy ri nitikïr nujäq ximonel
+
+ Majun chi ke ri taq chokoy k\'o pan awokisaxel nitikïr nujäq re ximonel re\'. Yatikïr yatel pa %1$s richin %2$s nitikïr jun.
+
+ ¿La Nel pa Ichinan Okem pa K\'amaya\'l?
+
+
+ %1$s k\'ison
+
+
+ Tijaq
+
+
+
+
+
+
+
+
+
+
+ ¡Xtz\'aqatisäx pa ruq\'a\'!
+
+ Man xilitäj ta ri ruk\'u\'x samaj
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Titz\'apïx
+
+
+
+ Ütz apetik pa %1$s
+
+
+ Anin. Ichinan. Majun nrewaj.
+
+
+ Titikirisäx
+
+
+
+ %1$s man junam ta achi\'el kik\'in ch\'aqa\' chik okik\'amaya\'l
+
+
+ Niqayüj ri anatab\'al toq natz\'apij ri chokoy richin awichinanem.
+
+
+
+ Tab\'ana\' chi re ri %1$s achi\'el k\'o wi richin ye\'achajij ri taq atzij pa xab\'achike ximoj xtajäq.
+
+
+ Tiya\' kan achi\'el kanob\'äl k\'o wi
+
+
+ Tik\'o\'
+
+
+ Tinimär ruchuq\'a\' ri awichinem
+
+ Katik\'o pa ri jun chik ruxaq ri ichinan okem pa k\'amaya\'l. Ke\'aq\'ata\' ri taq rutzijol chuqa\' juley chik taq etamab\'äl ri yetikïr yatkojqaj rik\'in ruxaq k\'amaya\'l chuqa\' nuch\'öb\' ri ramaj nisamäj ri ruxaq.
+
+
+ Akanoxik, ab\'ey
+
+ ¿La jun wi ri nakanoj? Tacha\' jun chik k\'wab\'äl kan k\'o wi pa Runuk\'ulem.
+
+
+ Tatz\'aqatisaj jun ruchojokem ri rutikirib\'al awäch
+
+ Katzolin pa ri ajowel taq ruxaq k\'amaya\'l pa %1$s aninäq. Tacha\' \"Titz\'aqatisäx pa ri Ruwäch Tikirib\'äl\" chupam ri %1$s cha\'osamaj.
+
+
+ Tib\'an chi ri ichinanem tok jun ruk\'ulun na\'oj
+
+ Tib\'an runuk\'ulem %1$s achi\'el ri okik\'amaya\'l k\'o wi richin nawïl rutzil ri ichinan okem pa k\'amaya\'l toq ye\'ajäq taq ruxaq k\'amaya\'l pa ch\'aqa\' chik taq chokoy.
+
+ ÜTZ, Wetaman chik
+ Tik\'o
+ Jun chik
+
+
+ -
+
+
+ Titz\'aqatisäx
+
+ JA\'
+
+
+ Tiq\'at
+
+ MANI
+
+
+ Nijaq ri chojokem akuchi\' chupun ri Nimirisan Ojqanem chuwäch Chajinïk
+
+
+ Ichinan okem pa k\'amaya\'l
+
+
+ Ri taq rutzijol nikiya\' q\'ij chawe ye\'ayüj el ri rumolojri\'ïl %1$s rik\'in jun chapoj. Man k\'atzinel ta najäq ri chokoy chuqa\' ri nusamajij ri awokik\'amaya\'l.
+
+
+ Tiyuj runatab\'al okem pa k\'amaya\'l
+
+
+ Taqasaj Firefox
+
+
+
+
+
+
+
+
+ Aj winäq Ya\'oj Ruq\'ij Mozilla chuqa\' ch\'aqa\' chik taq ya\'oj q\'ij kichin ch\'aqa\' chik taq jaqäl b\'itz\'ib\'.]]>
+
+
+ wawe\'.]]>
+
+
+ taq ya\'oj q\'ij.]]>
+
+
+ GNU General Public License v3, chuqa\' wachel wawe\' .]]>
+
+
+ Rub\'i\' okisanel
+ Ewan tzij
+ Tijosq\'ïx
+
+
+
+ Ütz Okem
+ Me\'utz Okem
+
+ Jikib\'an ruma: %1$s
+
+
+ Rujikomal Ruxaq K\'amaya\'l
+ K\'o chik ri URL
+
+
+ Tikanöx pa Ruxaq
+
+
+ Tikanöx pa ruxaq
+
+
+ %1$d/%2$d
+
+ %1$d richin %2$d
+
+
+ Tikanöx ri jun chik xilitäj
+
+ Tikanöx ri jun ilitajnäq kan
+
+ Tiqasäx tikanöx pa ruxaq
+
+
+
+
+ Tik\'utüx ruxaq ruk\'amaya\'l ajkematz\'ib\'
+
+
+ Ruxaq ch\'atal
+
+
+ URL xwachib\'ëx
+
+
+ Taq rusamajib\'al nuk\'unel
+
+
+ Kejaq taq ximonel pa taq chokoy
+
+
+ Q\'axinäq
+
+
+ Taq ruya\'oj q\'ij ri ruxaq
+
+
+ Kech\'utinirisäx kitzijol kuki
+
+
+ Titzij
+
+
+ Tichup
+
+
+ Kech\'utinirisäx kitzijol kuki
+
+
+ Ke\'atz\'eta\' jub\'a\' taq rutzijol k\'ayij rik\'in yexutüx ri taq kik\'utuxik kuki toq ke ri\' nrajo\'.
+
+ -->
+ Kech\'utinirisäx kitzijol kuki
+
+
+ TZIJÏL pa re ruxaq re\'
+
+
+ Wakami man koch\'el ta re ruxaq re\'
+
+
+ CHUPÜL pa re ruxaq re\'
+
+
+ Kech\'utinirisäx kitzijol kuki
+
+
+ CHUPÜL pa re ruxaq re\'
+
+
+ TZIJÏL pa re ruxaq re\'
+
+
+ ¿La nitzij ri kich\'utinisaxik rutzijol kuki richin %1$s?
+
+
+ ¿La nichup ri kich\'utinisaxik rutzijol kuki richin %1$s?
+
+
+ %1$s xkeruyüj ri taq rukuki re ruxaq chuqa\' xtuk\'ëx re ruxaq. Xkeruyüj ronojel ri taq kuki, nitikïr nutz\'apij ri molojri\'ïl o yerujäm ri taq ruch\'ich\' loq\'oj.
+
+
+ %1$s nitikïr nutojtob\'ej yeruxutuj pa ruyonil ri kik\'utuxik taq kuki.
+
+
+ Wakami re ruxaq re\' man nuköch\' ta ri kich\'utinisaxik kitzijol kuki. ¿La nawajo\' nak\'utuj chi re ri qamolaj richin nunik\'oj re ruxaq ajk\'amaya\'l re\' chuqa\' tutz\'aqatisaj tob\'äl ri chwa\'q kab\'ij?
+
+
+ Tiq\'at
+
+
+ Tik\'utüx to\'ïk
+
+
+ taq nuk\'ulem
+
+
+ Ruyon titzijtäj
+
+
+ Richin niya\' q\'ij chi re:
+
+
+ 1. Jät pa Runuk\'ulem Android
+
+
+ Taq Ya\'oj Q\'ij]]>
+
+
+ Tib\'e pa taq Nuk\'ulem
+
+
+ %1$s rik\'in TZIJÏL]]>
+
+
+ Elesäy wachib\'äl
+
+
+ Q\'asäy ch\'ab\'äl
+
+
+ K\'ojlib\'äl
+
+
+ Rutzijol
+
+
+ DRM-chajin rupam
+
+
+ Tik\'utüx q\'ij
+
+
+ Q\'aton
+
+
+ Xya\' q\'ij
+
+
+ Xq\'at ruma Android
+
+
+ Tiya\' q\'ij k\'oxom chuqa\' silowäch
+
+
+ Xa xe tiq\'at k\'oxom
+
+
+ Chilab\'en
+
+
+ Tiq\'at k\'oxom chuqa\' silowäch
+
+
+ Taq tijonïk
+
+
+ Firefox k\'o b\'ey nitikïr yeruyäk chuqa\' yerub\'än taq tijob\'alil.
+
+
+ Tetamäx ch\'aqa\' chik
+
+
+ Xtitz\'apitäj ri okisanel richin yejikib\'äx ri taq jaloj
+
+
+ Tiyuj
+
+
+ Tzijïl
+
+
+ Xtz\'aqatisäx
+
+
+ Näj chojmirisanem pa USB/Wi-Fi
+
+
+ Titzij chik
+
+
+ Tijaq ri ximonel pa jun k\'ak\'a\' molojri\'ïl
+
+
+ Ruwachib\'al retal ruwi\' q\'ab\'aj
+
+
+ "Man xetamäx ta ruwa ri rel ruwi\' q\'ab\'aj. Tatojtob\'ej chik."
+
+
+ Aninäq xsilon ri ruwi\' q\'ab\'aj. Tatojtob\'ej chik.
+
+
+ La yek\'ut pe ri taq chilab\'enïk richin yakanon?
+
+
+ Richin nik\'ul kichilab\'exik, %1$s k\'o chi nutäq ri natz\'ib\'aj pa ri rajtz\'ik ochochib\'äl.
+
+
+ Mani
+
+
+ Ja\'
+
+
+ Jujun taq kanob\'än man yatikïr ta yekik\'üt ta chilab\'enïk.
+
+
+ Tichup ruwäch
+
+
+
+
+ ¿La man pa rub\'eyal ta nisamäj re ruxaq k\'amaya\'l re\'?\n Tatojtob\'ej rik\'in nachüp ri Chajinïk chuwäch Ojqanem
+
+
+ Titz\'aqatisäx pa Tikirib\'äl tzuwäch"]]>
+
+
+ Kejaq konojel ri taq ximonel pa %1$s\n Tib\'an runuk\'ulem %1$s achi\'el ri awokik\'amaya\'l k\'o wi
+
+
+ Ruyon ke\'atz\'aqatisaj ri taq URLs kichin ri taq ruxan yalan ye\'awokisaj\n Tapitz\'itz\'ej xa b\'achike URL pa rukajtz\'ik ochochib\'äl
+
+
+ Tijaq jun ximonel pa jun k\'ak\'a\' ruwi\'\n Tapitz\'itz\'ej xab\'achike ximonel pa jun ruxaq
+
+
+ Tichup ri taq chilab\'enïk pa ri tikirib\'äl ruwäch
+
+
+ K\'ak\'a\' ruwi\' jaqon
+
+
+ Tiq\'ax pa
+
+
+ Nok pa chijun ruwa
+
+
+ Chanin tijal rik\'in tixim pa k\'ak\'a\' ruwi\'
+
+
+ Keq\'at taq ruxaq nïm kitz\'ilanem chuqa\' q\'olonela\'
+
+ Keq\'at taq ruxaq etaman chi yeq\'eleb\'en chuqa\' yeq\'olon, tz\'ilanel ruxaq taq chokoy chuqa\' itzel ruxaq taq solkema\'.
+
+
+ HTTPS-Only B\'anikil
+
+
+ Taq man relik ta
+
+ Xachüp ri Ruq\'atik Rupam pa re ajk\'amaya\'l taq ruxaq re\'.
+
+ Tiyuj
+
+ Keyuj ronojel ajk\'amaya\'l ruxaq
+
+
+ Keq\'at Kuki
+
+
+ Xsach ri Ruwi\'
+
+ Kojakuyu\', K\'o qak\'ayewal rik\'in re ruwi\' re\'.
+
+ Achi\'el jun ichinan okik\'amaya\'l, majub\'ey niqayäk chuqa\' man yojtikïr ta niqatzolij re ruwi\' re\'.
+
+ Titz\'apïx Ruwi\'
+
+
+
+
+
+ Titaq rutzijol sachoj chi re Mozilla
+
+
+
+
+ Rupam
+
+ Winaqinem
+
+ Utzirisan Chajinïk chuwäch Ojqanem
+
+ CHUPÜL ri chajinïk pa re ruxaq re\'
+
+ TZIJÏL ri chajinïk pa re ruxaq re\'
+
+ Rujikomal okem
+
+ Man ütz ta chi okem
+
+
+ Titzolin
+
+
+
+ Tiyuj
+
+ Tisik\'ïx chik
+
+ Tisik\'ïx chik
+
+ Rub\'i\' ri choj okem
+
+
+ Taq wachib\'äl eyakon chuqa\' ekomonin <b>manäq</b> xke\'elesäx toq xtayüj runatab\'al ri %1$s
+
+
+
+ Wachinel
+
+ Yuk\'unel
+
+ Q\'equ\'m
+
+ Xjikib\'äx ruma ri Yakonel Uchuq\'ab\'äl
+
+ Tojqäx ri ruwachinel oyonib\'äl
+
+
+ Man ütz ta okem
+
+
+
+ Titz\'apïx ruwi\'
+
+
+ Titz\'apïx elenel tzuwäch
+
+
+
+ ¡Atchajin!
+
+ Tewäx
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Titz\'apïx
+
+
+ Rukanoxik widget
+
+
+ Josq\'in kinatab\'al okem pa k\'amaya\'l 🎉
+
+
+ Titz\'aqatisäx widget pa ri rutikirib\'al ruwa
+
+
+ Xtz\'aqatisäx widget pa ri rutikirib\'al ruwa
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-co/strings.xml b/mobile/android/focus-android/app/src/main/res/values-co/strings.xml
new file mode 100644
index 0000000000..ab83478c6f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-co/strings.xml
@@ -0,0 +1,1125 @@
+
+
+
+
+
+
+
+
+ Abbandunà
+
+ Vai
+
+ Arregistrà
+
+
+ Ricerca o indirizzu
+
+ Navigazione privata autumatica.\nNavigate. Squassate. Ripitite.
+
+
+ A vostra cronolugia di navigazione hè stata squassata.
+ Cronolugia di navigazione squassata
+
+
+ A cronolugia di navigazione di l’unghjetta hè stata squassata.
+
+
+ Ricercà %1$s
+
+
+ Sparte…
+
+
+ Signalà un penseru nant’à stu situ
+
+
+ Apre cù %1$s
+
+
+ Apre cù…
+
+
+ Fissà à u screnu d’accolta
+
+
+ Aghjunghje à l’accurtatoghji
+
+ Caccià da l’accurtatoghji
+
+
+ Preferenze
+ Apprupositu
+ Aiutu
+ Diritti di l’utilizatore
+
+
+ Perseguitatori bluccati
+
+
+ Disattivà st’ozzione pò risolve qualchì penseru di u situ
+
+
+ Blucchime di cuntenutu
+
+ Disattivà per currege certi siti
+
+
+ Funziuneghja grazia à %1$s
+
+
+ Sparte cù
+
+ Squassà a cronolugia di navigazione ?
+ Picchichjate o spazzate sta nutificazione per squassà di manera sicura a vostra cronolugia di navigazione.
+
+
+ Picchichjate sta nutificazione o fatela sguillà per squassà di manera sicura a vostra cronolugia di navigazione.
+
+ Squassà a cronolugia di navigazione
+
+
+ Apre
+
+
+ Squassà è apre
+
+
+ Squassà
+
+
+ Squassà a cronolugia di navigazione
+
+
+
+ Squassà & apre
+
+
+ Squassà è apre %1$s
+
+
+
+ Circà in Focus
+
+ Circà in Klar
+
+ Circà in Focus Beta
+
+ Circà in Focus Nightly
+
+
+ %1$s vi aiuta à stà à e cumande.
+Impiegatelu cum’è un navigatore privatu :
+
+ Fate ricerche è navigate cù l’appiecazione
+ Bluccate i perseguitatori (o mudificà e preferenze per auturizalli)
+ Squassate i canistrelli è ancu e cronolugie di navigazione è di ricerca
+
+
+%1$s hè sviluppatu da Mozilla. A nostra missione hè d’incuragisce un Internet apertu è in bella saluta.
+Per sapene di più
]]>
+
+
+ Vita privata & sicurità
+
+
+ Traccie, canistrelli, scelta di dati cullettati
+
+
+ Mutore predefinitu, scrittura mezu-autumatica
+
+
+
+
+ Apprupositu di %1$s, aiutu
+
+
+ Prutezzione rinfurzata contr’à u spiunagiu
+
+
+ Cuntenutu di u Web
+
+
+ Navigazione trà l’appiecazioni
+
+
+ Generale
+
+
+ Navigatore predefinitu, lingua
+
+
+ Culletta di dati è adopru
+
+ Ricerca
+
+
+ Ottene suggestioni di ricerca
+
+ %1$s manderà à u vostru mutore di ricerca ciò chì voi stampittate in a barra d’indirizzu
+
+
+ Predefinitu
+
+
+ Mutore di ricerca
+
+
+ Attivata
+
+
+ Disattivata
+
+
+ Scrittura mezu-autumatica di l’indirizzi
+
+
+ Per i siti i più populari
+
+
+ Attivà per chì %s compii autumaticamente più di 450 indirizzi populari in a barra d’indirizzu.
+
+
+ Per i siti chì voi aghjunghjite
+
+
+ Attivà per chì %s compii autumaticamente i vostri indirizzi web favuriti.
+
+
+ Urganizà i siti
+
+
+ Urganizà i siti
+
+
+ + Aghjunghje un indirizzu persunalizatu
+
+
+ A vostra lista di scrittura mezu-autumatica :
+
+
+ Aghjunghje l’indirizzu
+
+
+ Aghjunghje un indirizzu persunalizatu
+
+
+ Aghjunghje un indirizzu persunalizatu
+
+
+ Aghjunghje un liame à a scrittura mezu-autumatica
+
+
+ Canistrelli è dati di siti
+
+
+ Dati cullettati
+
+
+ Caccià indirizzi persunalizati
+
+
+ Per sapene di più
+
+
+ Aghjunghje è urganizà indirizzi persunalizati per a scrittura mezu-autumatica.
+
+
+ Indirizzu web à aghjunghje
+
+
+ Incullà o stampittà un indirizzu
+
+
+ Esempiu : mozilla.org
+
+
+ Esempiu : esempiu.com
+
+
+ Novu indirizzu persunalizatu aghjuntu.
+
+
+ Caccià
+
+
+ Caccià
+
+
+ Ci vole à vérificà l’indirizzu stampittatu.
+
+ Lingua
+
+ Valore predefinitu di u sistema
+
+ Vita privata
+ Bluccà i perseguitatori publicitarii
+ Parechje publicità spiunanu e vostre visite nant’à i siti, ancu senza cliccà nant’à e publicità
+ Bluccà i perseguitatori di statistiche
+ Ghjovanu à cullettà, analizà è misurà e vostre attività cum’è picchichjà nant’à u screnu o fà sfilà e pagine
+ Bluccà i perseguitatori di rete suciale
+ Sò framessi in qualchì situ per spiunà e vostre visite o affissà funzioni cum’è buttoni di spartera
+ Bluccà l’altri perseguitatori di cuntenutu
+ Attivà st’ozzione pò cagiunà penseri cù certe pagine
+ Bluccà i canistrelli
+
+
+ Innò, vi ringraziemu
+ Bluccà solu i canistrelli terzi impiegati per u spiunagiu
+ Bluccà solu i canistrelli terzi
+
+ Bluccà i canistrelli intersiti
+ Sì, per piacè
+
+
+ Impiegà un’impronta digitale per spalancà l’appiecazione
+
+
+ Spalancate cù l’impronta digitale s’è vo avete aghjuntu accurtatoghji o quandu un situ web hè dighjà apertu in %s.
+
+
+ Modu furtivu
+
+ Piattà e pagine web quandu si cambia d’appiecazione è impedisce e catture di screnu.
+
+ Sicurità
+
+ Perfurmenze
+ Bluccà e grafie web
+
+ Pò impedisce certe icone o fiure d’affissassi
+
+ Bluccà JavaScript
+
+ E pagine ponu apressi più prestu, ma ponu dinù cumpurtassi d’una manera inaspettata
+
+
+ Sceglie %1$s cum’è navigatore predefinitu
+
+ Mozilla
+ Mandà dati d’adopru
+
+
+ Per sapene di più
+
+
+ Mozilla face casu à cullettà solu i dati chì sò ghjuvevule per rigalà è bunificà %1$s per tutti.
+
+
+ Pulitica di cunfidenzialità
+
+
+ Infurmazione di licenza
+
+
+ Bibliuteche impiegate
+
+
+ %s | Bibliuteche di fonte aperta
+
+
+ Apprupositu di %1$s
+
+
+ Mutori di ricerca installati
+
+
+ Sceglie u mutore di ricerca
+
+
+ Risturà i mutori predefiniti
+
+
+ + Aghjunghje un altru mutore di ricerca
+ Caccià mutori di ricerca
+ Caccià
+
+
+ Aghjunghje un altru mutore di ricerca
+
+ Selezziunate u vostru mutore di ricerca preferitu :
+
+
+ Aghjunghje un mutore di ricerca
+
+ Nome di u mutore di ricerca
+ Catena di ricerca à impiegà
+ Arregistrà
+
+
+ Esempiu : example.com/search/?q=%s
+
+ Novu mutore di ricerca aghjuntu.
+
+ Stampittà u nome di u mutore di ricerca
+ Un mutore di ricerca di u listessu nome hè dighjà installatu.
+
+ Stampittà una catena di ricerca
+
+ Verificate chì a catena di ricerca seguiteghji u furmatu di l’esempiu
+
+
+ Squassà u testu stampittatu
+
+
+ Ignurà
+
+
+ Squassà a cronolugia di navigazione
+
+
+ Unghjette aperte : %1$s
+
+
+ Cunnessione assicurizata
+
+
+ Caricamentu
+
+
+ Situ web caricatu
+
+
+ Ozzioni addiziunale
+
+
+ Buttone per affissà più d’ozzioni
+
+
+ Seguente in a cronolugia
+
+
+ Attualizà a pagina web
+
+
+ Precedente in a cronolugia
+
+
+ Piantà u caricamentu di u situ web
+
+
+ Rivene à l’appiecazione precedente
+
+
+ Numeru di perseguitatori bluccati
+
+
+ Bluccà i perseguitatori
+
+ Diritti di l’utilizatore
+
+ Apre u liame in un’altra appiecazione
+
+ Avete da lascià %1$s per apre stu liame in %2$s.
+
+ Truvà un’appiecazione capace d’apre stu liame
+
+ Alcuna appiecazione di u vostru apparechju ùn hè capace d’apre stu liame. Avete da lascià %1$s per circà in %2$s un appiecazione chì ne seria capace.
+
+ Fermà a navigazione privata ?
+
+
+ %1$s compiu
+
+
+ Apre
+
+
+
+
+
+
+
+
+
+
+ Aghjuntu à l’accurtatoghji !
+
+ Indirizzu intruvevule
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Chjode
+
+
+
+ Benvenuta in %1$s
+
+
+ Rapidu. Pruvatu. Senza distrazzione.
+
+
+ Principià
+
+
+
+ %1$s ùn hè micca cum’è l’altri navigatori
+
+
+ Per più di cunfidenzialità, squassemu a vostra cronolugia quandu si chjode l’appiecazione.
+
+
+
+ Fate di %1$s u vostru navigatore predefinitu per prutege i vostri dati à l’apertura d’ogni liame.
+
+
+ Sceglie cum’è navigatore predefinitu
+
+
+ Ignurà
+
+
+
+ Rinfurzà a vostra vita privata
+
+ A navigazione privata passa à u livellu insù. Bluccate e publicità è altri perseguitatori chì vi spiunanu è rallentanu u caricamentu di e pagine web.
+
+
+ Ricercate à a vostra manera
+
+ Ricercate qualcosa di sfarente ? Sciglite un altru mutore di ricerca predefinitu in e Preferenze.
+
+
+ Aghjunghjite accurtatoghji à u vostru screnu d’accolta
+
+ Rivultate à i vostri siti favuriti in %1$s prestu prestu. Selezziunate puru « Fissà à u screnu d’accolta » da u listinu di %1$s.
+
+
+ Ripigliate in manu a vostra vita privata
+
+ Sciglite %1$s cum’è u vostru navigatore predefinitu è cusì a navigazione serà privata quandu e pagine web s’apreranu da d’altre appiecazioni.
+
+ Iè, aghju capitu !
+ Ignurà
+ Seguente
+
+
+ -
+
+
+ Aghjunghje
+
+
+ SÌ
+
+
+ Abbandunà
+
+
+ INNÒ
+
+
+ L’accurtatoghju s’aprerà cù a prutezzione rinfurzata contr’à u spiunagiu disattivata
+
+
+ Sessione di navigazione privata
+
+
+ Da e nutificazioni si pò squassà a sessione di %1$s cù una picchicciata. Ùn ci hè bisognu d’apre l’appiecazione ne d’affissà ciò chì si passa in u vostru navigatore.
+
+
+ Squassà a cronolugia di navigazione
+
+
+ Scaricà Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License è d’altre licenze di tipu fonte aperta.]]>
+
+
+ quì.]]>
+
+
+ licenze libere è fonte aperta.]]>
+
+
+ GNU General Public License v3, è dispunibule quì .]]>
+
+
+ Nome d’utilizatore
+ Parolla d’intesa
+ Squassà
+
+
+
+ Cunnessione assicurizata
+ Cunnessione micca assicurizata
+
+ Verificatu da : %1$s
+
+
+ Sicurità di u situ
+ L’indirizzu esiste dighjà
+
+
+ Circà in a pagina
+
+
+ Circà in a pagina
+
+
+ %1$d/%2$d
+
+ %1$d nant’à %2$d
+
+
+ Truvà u risultatu seguente
+
+ Truvà u risultatu precedente
+
+ Chjode a ricerca in a pagina
+
+
+
+
+ Versione d’urdinatore
+
+
+ Versione urdinatore
+
+
+ Indirizzu web cupiatu
+
+
+ Attrezzi di sviluppu
+
+
+ Apre i liami in l’appiecazioni
+
+
+ Espertu
+
+
+ Permessi di situ
+
+
+ Riduzzione di e striscie di cannistrelli
+
+
+ Attivata
+
+
+ Disattivata
+
+
+ Riduzzione di e striscie di cannistrelli
+
+
+ Fighjate menu di striscie righjittendu autumaticamente e dumande di canistrelli quand’ellu hè pussibule.
+
+ -->
+ Riduzzione di e striscie di cannistrelli
+
+
+ ATTIVATA per stu situ
+
+
+ Attualmente u situ ùn hè micca accettatu
+
+
+ DISATTIVATA per stu situ
+
+
+ Riduzzione di e striscie di cannistrelli
+
+
+ DISATTIVATA per stu situ
+
+
+ ATTIVATA per stu situ
+
+
+ Attivà a riduzzione di e striscie di cannistrelli per %1$s ?
+
+
+ Disattivà a riduzzione di e striscie di cannistrelli per %1$s ?
+
+
+ %1$s squasserà i canistrelli di stu situ è attualizerà a pagina. A squassatura di tutti i canistrelli puderia discunnettevi o viutà e sporte di comprera.
+
+
+ %1$s pò pruvà di righjittà autumaticamente e dumande di canistrelli.
+
+
+ Ora, stu situ ùn hè micca accettatu da a riduzzione di e striscie di cannistrelli. Vulete fà una dumanda à a nostra squadra per verificà stu situ web è accettallu in u futuru ?
+
+
+ Abbandunà
+
+
+ Richiede ch’ellu sia accettatu
+
+
+ A dumanda per chì u situ sia accettatu hè stata mandata.
+
+
+ A dumanda per chì u situ sia accettatu hè stata mandata.
+
+
+
+ %1$s prova di righjittà e dumande di canistrelli per chjode e striscie strazievule di canistrelli.\n\nSceglie i parametri per e striscie di canistrelli in e %2$s.
+
+ preferenze
+
+
+ Leghje autumaticamente l’elementi multimedià
+
+
+ Per permettelu :
+
+
+ 1. Andate à e preferenze Android
+
+
+ Permessi]]>
+
+
+ Andà à e preferenze
+
+
+ %1$s]]>
+
+
+ Apparechju-fotò
+
+
+ Microfonu
+
+
+ Lucalizazione
+
+
+ Nutificazioni
+
+
+ Cuntenutu cuntrollatu da DRM
+
+
+ Dumandà per permette
+
+
+ Bluccatu
+
+
+ Permessu
+
+
+ Bluccatu da Android
+
+
+ Permette l’audio è a video
+
+
+ Bluccà solu l’audio
+
+
+ Ricumandatu
+
+
+ Bluccà l’audio è a video
+
+
+ Studii
+
+
+ Firefox pò installà è lancià studii di quandu in quandu.
+
+
+ Per sapene di più
+
+
+ L’appiecazione hà da piantà per appiecà i cambiamenti
+
+
+ Caccià
+
+
+ Attivi
+
+
+ Compii
+
+
+ Spannatura alluntanata via USB/Wi-Fi
+
+
+ Sbluccà
+
+
+ Cunfirmate cù a vostra impronta digitale
+
+
+ Pudete impiegà a vostra impronta digitale per cuntinuà a sessione currente di l’appiecazione.
+
+
+ Apre u liame in una nova sessione
+
+
+ Icona d’impronta digitale
+
+
+ Impronta digitale micca ricunnisciuta. Pruvà torna.
+
+
+ U ditu hà mossu troppu prestu. Pruvà torna.
+
+
+ Affissà e suggestioni di ricerca ?
+
+
+ Per ottene suggestioni di ricerca, %1$s hà bisognu di mandà à u mutore di ricerca ciò chì vo stampittate in a barra d’indirizzu.
+
+
+ Nò
+
+
+ Sì
+
+
+ Certi mutori di ricerca ùn ponu affissà suggestioni.
+
+
+ Ignurà
+
+
+
+
+ Stu situ ùn si cumporta micca cum’è previstu ?\n
+ Pruvate di disattivà a prutezzione contr’à u spiunagiu
+
+
+ Fissà à u screnu d’accolta]]>
+
+
+ Apre ogni liame in %1$s\n
+ Sceglie %1$s cum’è navigatore predefinitu
+
+
+
+ Compie autumaticamente l’indirizzi di i siti i più visitati\n Effettuate una longa incalcata nant’à un indirizzu in a barra
+
+
+ Apre un liame in una nova unghjetta\n
+ Effettuate una longa incalcata nant’à un liame in una pagina
+
+
+
+ Disattivà e minichichje nant’à u screnu d’avviu
+
+
+ Nova unghjetta aperta
+
+
+ Affissà
+
+
+ Modu di screnu sanu attivatu
+
+
+ Affissà subitu ogni liame apertu in una nova unghjetta
+
+
+ Bluccà i siti chì puderianu esse periculosi o ingannatori
+
+ Bluccà i siti animosi è ingannatori, i siti di prugrammi dispiacevule è indesiderevule.
+
+
+ Modu solu HTTPS
+
+
+ Tentativu autumaticu di cunnessione à i siti impieghendu u protocollu di cifratura HTTPS per aumentà a sicurità.
+
+
+ Eccezzioni
+
+ Avete disattivatu u blucchime di cuntenutu per quessi siti web.
+
+ Caccià
+
+ Caccià tutti i siti web
+
+
+ Bluccà i canistrelli
+
+
+ Vulete bluccà i canistrelli ?
+
+
+ L’unghjetta hè rotta
+
+ Per disgrazia, avemu un prublema cù st’unghjetta.
+
+ Cum’è a navigazione era privata, ùn avemu micca arregistratu l’unghjetta è dunque ùn pudemu micca risturalla.
+
+ Chjode l’unghjetta
+
+
+
+
+
+ Mandà un raportu d’accidente à Mozilla
+
+
+
+
+ Perseguitatori bluccati dapoi u %s
+
+ Cuntenutu
+
+ Publicità
+
+ Suciale
+
+ Statistica
+
+ Prutezzione rinfurzata contr’à u spiunagiu
+
+ E prutezzioni sò DISATTIVATE per stu situ
+
+ E prutezzioni sò ATTIVATE per stu situ
+
+ A cunnessione hè assicurata
+
+ A cunnessione ùn hè micca assicurata
+
+ Perseguitatori è scenarii à bluccà
+
+
+ Ritornu
+
+
+
+ Caccià
+
+
+ Rinuminà
+
+ Rinuminà
+
+
+ Nome di l’accurtatoghju
+
+
+ E fiure sparte o arregistrate <b>ùn sò micca squassate</b> quandu a cronolugia di %1$s hè squassata
+
+
+
+ Tema
+
+ Chjaru
+
+ Scuru
+
+ Definitu da l’ecunumizatore d’energia di batteria
+
+ Seguità u tema di l’apparechju
+
+
+
+ Stu situ ùn permette micca u modu HTTPS
+
+
+ Per sapene di più
+ Cambiate sta definizione in Preferenze > Vita privata è sicurità > Sicurità.]]>
+
+
+ Cunnessione micca assicurata
+
+
+
+ S’è vo avete dighjà riesciutu à cunnettevi à stu servitore, u sbagliu pò esse timpurariu.
+ ]]>
+
+
+ Qualchissia puderia pruvà d’impatruniscesi di u situ web è cuntinuà puderia esse periculosu.
+
+ %1$s ùn hà micca fidanza in %2$s perchè l’emettore di u so certificatu hè scunnisciutu, u certificatu hè firmatu daperellu, o ancu u servitore ùn manda micca certificati intermediarii curretti.
+ ]]>
+
+
+
+ Chjode l’unghjetta
+
+
+
+ Missione compia ! Avemu fermatu u spiunagiu da stu situ. Picchichjate u scudu à ogni mumentu per vede ciò chì no blucchemu.
+
+
+ Chjode a finestra popup
+
+
+
+ Site prutetti !
+
+
+ Ste preferenze predefinite furniscenu una prutezzione forte. Ma hè faciule di mudificalle per suddisfà i vostri bisogni specifichi.
+
+ Righjittà
+
+
+ Picchichjate quì per squassà tuttu - cronolugia, canistrelli, ecc. - è principiate à zeru in un’unghjetta nova.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Chjode
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget di ricerca
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Cronolugia di navigazione squassata ! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Principiate a vostra sessione di navigazione privata è noi bluccheremu l’elementi di spiunagiu è l’altre minacce durante a navigazione.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Vi lasciemu à a vostra navigazione privata, ma principiate più prestu e prossime volte cù u widget %1$s nant’à u vostru screnu d’accolta.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Aghjunghje u widget à u screnu d’accolta
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget aghjuntu à u screnu d’accolta
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-cs/strings.xml b/mobile/android/focus-android/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000000..0e009bfec1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,1113 @@
+
+
+
+
+
+
+
+
+ Zrušit
+
+ OK
+
+ Uložit
+
+
+ Zadejte hledání nebo adresu
+
+ Automatické anonymní prohlížení.\nProhlížejte. Vymažte. Opakujte.
+
+
+ Vaše historie prohlížení byla vymazána.
+ Historie prohlížení vymazána
+
+
+ Historie prohlížení panelu byla vymazána.
+
+
+ Hledat %1$s
+
+
+ Sdílet…
+
+
+ Nahlášení chyby stránky
+
+
+ Otevřít v aplikaci %1$s
+
+
+ Otevřít v…
+
+
+ Přidat na domovskou obrazovku
+
+
+ Přidat zkratku
+
+ Odebrat ze zkratek
+
+
+ Nastavení
+ O nás
+ Nápověda
+ Vaše práva
+
+
+ Zablokováno sledovacích prvků
+
+
+ Vypnutí této funkce může vyřešit některé problémy se stránkami
+
+
+ Blokování obsahu
+
+ Vypněte pro opravu některých problémů se stránkami
+
+
+ Pohání %1$s
+
+
+ Sdílet přes
+
+ Vymazat historii prohlížení?
+ Klepnutím na toto oznámení nebo jeho smazáním bezpečně vymažete svou historii prohlížení.
+
+
+ Klepnutím nebo přejetím po tomto oznámení bezpečně vymažete historii procházení.
+
+ Vymazat historii prohlížení
+
+
+ Otevřít
+
+
+ Vymazat a otevřít
+
+
+ Vymazat
+
+
+ Vymazat historii prohlížení
+
+
+
+ Vymazat a otevřít
+
+
+ Vymazat a otevřít %1$s
+
+
+
+ Vyhledat ve Focusu
+
+ Vyhledat v Klaru
+
+ Vyhledat v betaverzi Focusu
+
+ Vyhledat ve Focusu Nightly
+
+
+ %1$s vám dává kontrolu.
+Používejte ho jako soukromý prohlížeč:
+
+ Vyhledávejte a prohlížejte přímo v aplikaci
+ Blokujte sledovací prvky (nebo aktualizujte nastavení pro povolení sledovacích prvků)
+ Tlačítkem smažte cookies a historii vyhledávání a prohlížení
+
+
+%1$s je vyvíjen Mozillou. Naší misí je podporovat zdravý a otevřený internet.
+Zjistěte více
]]>
+
+
+ Soukromí a zabezpečení
+
+
+ Sledování, soubory cookie, možnosti hlášení
+
+
+ Výchozí vyhledávač, doplňování
+
+
+
+
+ O aplikaci %1$s, nápověda
+
+
+ Rozšířená ochrana proti sledování
+
+
+ Webový obsah
+
+
+ Přepínání aplikací
+
+
+ Obecné
+
+
+ Výchozí prohlížeč, jazyk
+
+
+ Sběr dat a jejich použití
+
+ Hledat
+
+
+ Povolit našeptávání
+
+ %1$s bude odesílat text zadávaný to adresního řádku vyhledávači
+
+
+ Výchozí
+
+
+ Vyhledávací modul
+
+
+ Zapnuto
+
+
+ Vypnuto
+
+
+ Automatické doplňování URL adres
+
+
+ Pro Top stránky
+
+
+ Po zapnutí bude %s našeptávat více než 450 populárních URL adres.
+
+
+ Pro stránky které přidáte
+
+
+ Povolí aplikaci %s našeptávání vámi vybraných adres stránek.
+
+
+ Spravovat stránky
+
+
+ Spravovat stránky
+
+
+ + Přidat vlastní URL adresu
+
+
+ Váš seznam automatického doplňování:
+
+
+ Přidat URL
+
+
+ Přidat vlastní URL adresu
+
+
+ Přidat vlastní URL adresu
+
+
+ Přidat odkaz na našeptávání
+
+
+ Cookies a data stránek
+
+
+ Možnosti hlášení
+
+
+ Odebrat vlastní URL adresy
+
+
+ Zjistit více
+
+
+ Přidání a správa vlastních URL adres pro automatické doplňování.
+
+
+ URL adresa k přidání
+
+
+ Vložte nebo zadejte URL adresu
+
+
+ Příklad: mozilla.cz
+
+
+ Příklad: example.com
+
+
+ Vlastní URL adresa přidána.
+
+
+ Odebrat
+
+
+ Odebrat
+
+
+ Zkontrolujte zadanou URL adresu.
+
+ Jazyk
+
+ Systémové nastavení
+
+ Soukromí
+ Blokovat sledující reklamy
+ Některé reklamy sledují návštěvy stránek i v případě, že na reklamy nekliknete
+ Blokovat analytické prvky
+ Slouží k shromážďování, analýze a měření aktivit jako je klepnutí nebo posouvání
+ Blokovat sledovací prvky sociálních sítí
+ Vloženo do stránek pro sledování vašich návštěv a zobrazování funkcí jako tlačítka pro sdílení
+ Blokovat ostatní sledovací prvky
+ Zapnutí může způsobit, že se některé stránky budou chovat neočekávaně
+ Blokovat cookies
+
+
+ Ne, díky
+ Blokovat pouze sledovací cookies třetích stran
+ Blokovat pouze cookies třetích stran
+ Blokovat cross-site cookies
+ Ano, prosím
+
+
+ Odemknout aplikaci otiskem prstu
+
+
+ Odemkněte pomocí otisku prstu, pokud jste přidali Zkratky nebo pokud je webová stránka již otevřena v %s.
+
+
+ Nenápadnost
+
+ Skrýt webové stránky při přepínání aplikací a zabránit pořizování snímků obrazovky.
+
+ Zabezpečení
+
+ Výkon
+ Blokovat webové fonty
+
+ Některé ikony nebo obrázky se nemusí zobrazovat
+
+ Blokovat JavaScript
+
+ Stránky se mohou načítat rychleji, ale také nemusí správně fungovat
+
+
+ Nastavit %1$s jako výchozí prohlížeč
+
+ Mozilla
+ Odesílat údaje o používání
+
+
+ Zjistit více
+
+
+ Mozilla sbírá jenom ty informace, které potřebuje pro další vylepšování aplikace %1$s.
+
+
+ Zásady ochrany osobních údajů
+
+
+ Informace o licenci
+
+
+ Knihovny, které používáme
+
+
+ %s | OSS knihovny
+
+
+ O aplikaci %1$s
+
+
+ Nainstalované vyhledávací moduly
+
+
+ Vyberte vyhledávač
+
+
+ Obnovit výchozí vyhledávací moduly
+
+
+ + Přidat jiný vyhledávací modul
+ Odebrat vyhledávací moduly
+ Odebrat
+
+
+ Přidat další vyhledávač
+
+ Vyberte preferovaný vyhledávač:
+
+
+ Přidat vyhledávací modul
+
+ Název vyhledávacího modulu
+ Použít řetězec pro vyhledávání
+ Uložit
+
+
+ Příklad: example.com/search/?q=%s
+
+ Nový vyhledávací modul byl přidán.
+
+ Zadejte název vyhledávače
+ Tento název už je používán nainstalovaným vyhledávacím modulem.
+
+ Zadejte hledaný řetězec
+
+ Zkontrolujte, zda vyhledávací řetězec odpovídá formátu příkladu
+
+
+ Vymazat pole
+
+
+ Zavřít
+
+
+ Vymazat historii prohlížení
+
+
+ Počet otevřených panelů: %1$s
+
+
+ Zabezpečené připojení
+
+
+ Načítání
+
+
+ Stránka načtena
+
+
+ Další možnosti
+
+
+ Tlačítko Další možnosti
+
+
+ Přejít vpřed
+
+
+ Obnovit stránku
+
+
+ Návrat zpět
+
+
+ Zastavit načítání stránky
+
+
+ Návrat do předchozí aplikace
+
+
+ Celkový počet zablokovaných sledovacích prvků
+
+
+ Blokovat sledovací prvky
+
+ Vaše práva
+
+ Otevřít odkaz v jiné aplikaci
+
+ Můžete opustit aplikaci %1$s a otevřít tento odkaz v aplikaci %2$s.
+
+ Najít aplikaci, která může otevřít odkaz
+
+ Žádná z aplikací na vašem zařízení není schopná otevřít tento odkaz. Můžete opustit aplikaci %1$s a najít vhodnou aplikaci v obchodu %2$s.
+
+ Opustit režim anonymního prohlížení?
+
+
+ Soubor %1$s byl stažen
+
+
+ Otevřít
+
+
+
+
+
+
+
+
+
+
+ Přidáno do zkratek
+
+ Server nebyl nalezen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zavřít
+
+
+
+ Vítá vás %1$s
+
+
+ Rychlost. Soukromí. Žádné rozptylování.
+
+
+ Začít
+
+
+
+ %1$s není jako ostatní prohlížeče
+
+
+ Pro zvýšení vašeho soukromí vymažeme při zavření aplikace vaši historii.
+
+
+
+ Nastavte si aplikaci %1$s jako výchozí a chraňte tak svá data při každém otevření odkazu.
+
+
+ Nastavit jako výchozí prohlížeč
+
+
+ Přeskočit
+
+
+
+ Posuňte své soukromí na vyšší úroveň
+
+ Anonymní prohlížení dostalo nový rozměr. Blokuje reklamy a další obsah, který vás sleduje napříč stránkami a zpomaluje načítání.
+
+
+ Vaše hledání, vaše cesta
+
+ Hledáte něco jiného? V nastavení si zvolte jiný vyhledávací modul jako výchozí.
+
+
+ Přidejte si zkratky na vaši domovskou obrazovku
+
+ S aplikací %1$s se můžete rychle vrátit ke svým oblíbeným stránkám. Použijte „Přidat na domovskou obrazovku“ z nabídky aplikace %1$s.
+
+
+ Udělejte ze soukromí zvyk
+
+ Nastavte si %1$s jako svůj výchozí prohlížeč a získejte výhody soukromého prohlížení kdykoliv otevřete odkaz z ostatních aplikací.
+
+ OK, rozumím!
+ Přeskočit
+ Další
+
+
+ -
+
+
+ Přidat
+
+ ANO
+
+
+ Zrušit
+
+ NE
+
+
+ Zkratka se bude otevírat s vypnutou ochranou proti sledování
+
+
+ Relace anonymního prohlížení
+
+
+ Oznámení aplikace %1$s vám umožní smazat historii jedním klepnutím. Nemusíte ani otevírat aplikaci nebo se dívat, co máte zrovna otevřeno.
+
+
+ Vymazat historii prohlížení
+
+
+ Stáhnout Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License a jiných open source licencí.]]>
+
+
+ zde.]]>
+
+
+ licencemi svobodného softwaru.]]>
+
+
+ GNU General Public License v3 a dostupné zde .]]>
+
+
+ Uživatelské jméno
+ Heslo
+ Vymazat
+
+
+
+ Zabezpečené připojení
+ Nezabezpečené připojení
+
+ Ověřil: %1$s
+
+
+ Zabezpečení stránky
+ URL adresa už existuje
+
+
+ Najít na stránce
+
+
+ Najít na stránce
+
+
+ %1$d/%2$d
+
+ %1$d z %2$d
+
+
+ Další výskyt
+
+ Předchozí výskyt
+
+ Ukončit hledání na stránce
+
+
+
+
+ Verze stránky pro počítač
+
+
+ Verze pro počítač
+
+
+ URL adresa zkopírována
+
+
+ Vývojářské nástroje
+
+
+ Otevírat odkazy v aplikacích
+
+
+ Rozšířené
+
+
+ Oprávnění serverů
+
+
+ Omezení cookie lišt
+
+
+ Zapnuto
+
+
+ Vypnuto
+
+
+ Omezení cookie lišt
+
+
+ Omezuje zobrazování cookie lišt a bannerů odmítnutím jejich souhlasu, kdykoliv to je možné.
+
+ -->
+ Omezení cookie lišt
+
+
+ ZAPNUTO pro tento web
+
+
+ Server není aktuálně podporován
+
+
+ VYPNUTO pro tento web
+
+
+ Omezení cookie lišt
+
+
+ VYPNUTO pro tento web
+
+
+ ZAPNUTO pro tento web
+
+
+ Chcete zapnout omezení cookie lišt pro %1$s?
+
+
+ Chcete vypnout omezení cookie lišt pro %1$s?
+
+
+ %1$s smaže cookies tohoto webu a stránku znovu načte. Vymazání všech cookies může způsobit vaše odhlášení nebo třeba vyprázdnění nákupního koše.
+
+
+ %1$s se může pokusit automaticky odmítnout žádosti o povolení cookies.
+
+
+ Na tomto webu v tuto chvíli není omezení cookie lišt podporováno. Chcete náš tým požádat o kontrolu této stránky a za účelem budoucího přidání podpory?
+
+
+ Zrušit
+
+
+ Požádat o přidání podpory
+
+
+ Žádost o přidání podpory tohoto serveru byla odeslána.
+
+
+ Žádost o přidání podpory tohoto serveru byla odeslána.
+
+
+
+ %1$s se pokouší odmítnout požadavky na soubory cookie, aby odmítl otravné bannery cookie.\n\nSpravujte předvolby lišt cookie v %2$s.
+
+ nastavení
+
+
+ Automatické přehrávání
+
+
+ Pro povolení:
+
+
+ 1. Otevřete nastavení systému Android
+
+
+ Oprávnění]]>
+
+
+ Přejít do nastavení
+
+
+ %1$s na ZAP]]>
+
+
+ Fotoaparát
+
+
+ Mikrofon
+
+
+ Umístění
+
+
+ Oznámení
+
+
+ Obsah chráněný pomocí DRM
+
+
+ Vždy se zeptat
+
+
+ Blokováno
+
+
+ Povoleno
+
+
+ Blokováno Androidem
+
+
+ Povolit zvuk i video
+
+
+ Blokovat automatické přehrávání zvuků
+
+
+ Doporučené
+
+
+ Blokovat automatické přehrávání zvuků i videí
+
+
+ Studie
+
+
+ Firefox může čas od času instalovat a spouštět studie.
+
+
+ Zjistit více
+
+
+ Pro použití změn se aplikace ukončí
+
+
+ Odebrat
+
+
+ Aktivní
+
+
+ Dokončeno
+
+
+ Vzdálené ladění pomocí USB//Wi-Fi
+
+
+ Odemknout
+
+
+ Potvrďte pomocí otisku prstu
+
+
+ Pro pokračování aktuální relace můžete použít otisk prstu.
+
+
+ Otevřít odkaz v nové relaci
+
+
+ Ikonu otisku prstu
+
+
+ Otisk prstu nebyl rozpoznán. Zkuste to znovu.
+
+
+ Pohyb prstem byl příliš rychlý. Zkuste to znovu.
+
+
+ Našeptávat dotazy pro vyhledávač?
+
+
+ K realizaci našeptávání musí %1$s text napsaný do adresního řádku odesílat zvolenému vyhledávači.
+
+
+ Ne
+
+
+ Ano
+
+
+ Některé vyhledávače nemohou návrhy zobrazit.
+
+
+ Zavřít
+
+
+
+
+ Nechová se stránka podle očekávání?\n Zkuste vypnout ochranu proti sledování
+
+
+ Přidat na domovskou obrazovku]]>
+
+
+ Otevírejte v aplikaci %1$s všechny odkazy\n Nastavte si %1$s jako výchozí prohlížeč
+
+
+ Nechcete si doplňovat nejpoužívanější adresy\n Podržte prst na adrese v adresním řádku
+
+
+ Otevírejte si odkazy v novém panelu\n Podržte prst na jakémkoliv odkazu na stránce
+
+
+ Vypnout tipy na domovské stránce
+
+
+ Nový panel otevřen
+
+
+ Přepnout
+
+
+ Spuštěn režim celé obrazovky
+
+
+ Přepnout ihned na odkaz v novém panelu
+
+
+ Blokovat potenciálně nebezpečné a klamavé stránky
+
+ Blokuje hlášené klamavé a útočné stránky, stránky s malwarem a stránky s nevyžádaným obsahem.
+
+
+ Režim „pouze HTTPS“
+
+
+ Pro zvýšení zabezpečení se automaticky pokusí připojit k webům pomocí šifrovacího protokolu HTTPS.
+
+
+ Výjimky
+
+ Na těchto stránkách jste blokování obsahu vypnuli.
+
+ Odebrat
+
+ Odebrat všechny stránky
+
+
+ Blokovat cookies
+
+
+ Chcete zablokovat cookies?
+
+
+ Panel spadl
+
+ Promiňte. S tímto panelem máme problém.
+
+ Jako soukromý prohlížeč tento panel nikdy neukládáme a nemůžeme obnovit.
+
+ Zavřít panel
+
+
+
+
+
+ Poslat zprávu o pádu Mozille
+
+
+
+
+ Sledovací prvky blokovány od %s
+
+ Obsah
+
+ Reklama
+
+ Sociální sítě
+
+ Analytika
+
+ Rozšířená ochrana proti sledování
+
+ Ochrana je na tomto serveru vypnuta
+
+ Ochrana je na tomto serveru zapnuta
+
+
+ Zabezpečené spojení
+
+ Spojení není zabezpečené
+
+ Blokované sledovací prvky a skripty
+
+
+ Zpátky
+
+
+
+ Odebrat
+
+
+ Přejmenovat
+
+ Přejmenovat
+
+
+ Název zkratky
+
+
+ Uložené a sdílené obrázky <b>nebudou</b> smazány když smažete historii aplikace %1$s
+
+
+
+ Vzhled
+
+ Světlý
+
+ Tmavý
+
+ Podle nastavení spořiče baterie
+
+ Podle nastavení zařízení
+
+
+
+ Tento server nepodporuje HTTPS
+
+
+ Dozvědět se více
+Toto nastavení můžete změnit v části Nastavení -> Soukromí a zabezpečení > Zabezpečení.]]>
+
+
+ Spojení není zabezpečené
+
+
+
+ Pokud jste se k tomuto serveru už v minulosti úspěšně připojili, je možná chyba jenom dočasná, a můžete to zkusit znovu později.
+ ]]>
+
+
+ Někdo se může snažit vydávat za zmiňovaný server a pokračovaní může být riskantní.
+
+ %1$s serveru %2$s nedůvěřuje, protože vydavatel zaslaného certifikátu je neznámý, certifikát je podepsaný sám sebou nebo server neposílá správné mezilehlé certifikáty.
+ ]]>
+
+
+
+ Zavřít panel
+
+
+
+ Máme je! Tomuto serveru jsme zabránili ve vašem sledování. Klepnutím na ikonu štítu se dozvíte, co bylo zablokováno.
+
+
+ Zavřít vyskakovací okno
+
+
+
+ Jste chráněni!
+
+ Tato výchozí nastavení poskytují silnou ochranu. Můžete je ale jednoduše nastavit podle vlastních potřeb.
+
+ Zavřít
+
+
+ Klepnutím sem vše vyhodíte do koše – historii, cookies, prostě vše – a začnete znovu v novém panelu.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Zavřít
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget vyhledávání
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Historie prohlížení vymazána 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Začněte svou relaci anonymního prohlížení a my budeme průběžně blokovat sledovací prvky a jiné zlé věci.
+
+
+ Necháme vás v anonymním prohlížení, ale příště můžete začít rychleji pomocí widgetu %1$s na Domovské obrazovce.
+
+
+ Přidat widget na domovskou obrazovku
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget přidán na domovskou obrazovku
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-cy/strings.xml b/mobile/android/focus-android/app/src/main/res/values-cy/strings.xml
new file mode 100644
index 0000000000..282a875345
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-cy/strings.xml
@@ -0,0 +1,1122 @@
+
+
+
+
+
+
+
+
+ Diddymu
+
+ Iawn
+
+ Cadw
+
+
+ Chwilio neu gyfeiriad gwe
+
+ Pori preifat awtomatig.\nPori. Dileu. Eto.
+
+
+ Mae eich hanes pori wedi cael ei ddileu.
+ Hanes pori wedi’i glirio
+
+
+ Mae hanes pori\'r Tab wedi cael ei ddileu.
+
+
+ Chwilio am %1$s
+
+
+ Rhannu…
+
+
+ Adrodd ar Fater Gwefan
+
+
+ Agor yn %1$s
+
+
+ Agor yn…
+
+
+ Ychwanegu i’r sgrin Cartref
+
+
+ Ychwanegu at y Llwybrau Byr
+
+ Tynnu o’r Llwybrau Byr
+
+
+ Gosodiadau
+ Ynghylch
+ Cymorth
+ Eich Hawliau
+
+
+ Rhwystro tracwyr
+
+
+ Gall ddiffodd hwn atal rhai problemau ar y wefan
+
+
+ Rhwystro Cynnwys
+
+ Diffodd i drwsio rhai gwefannau
+
+
+ Grym %1$s
+
+
+ Rhannu drwy
+
+ Dileu hanes pori?
+ Tapio neu gau\'r hysbysiad hwn i ddileu eich hanes pori yn ddiogel.
+
+
+ Tapio neu lusgo\'r hysbysiad hwn i ddileu eich hanes pori yn ddiogel.
+
+ Dileu hanes pori
+
+
+ Agor
+
+
+ Dileu ac Agor
+
+
+ Dileu
+
+
+ Dileu hanes pori
+
+
+
+ Dileu ac agor
+
+
+ Dileu ac agor %1$s
+
+
+
+ Chwilio o fewn Focus
+
+ Chwilio o fewn Klar
+
+ Chwilio o fewn Focus Beta
+
+
+ Chwilio o fewn Focus Nightly
+
+
+ Gyda %1$s chi sy’n rheoli.
+Defnyddiwch ef fel porwr preifat:
+
+ Chwilio a phori o fewn yr ap
+ Rhwystro tracwyr (neu ddiweddaru’ch gosodiadau i ganiatáu tracwyr)
+ Dileu er mwyn dileu cwcis yn ogystal â hanes chwilio a phori
+
+
+Mae %1$s yn cael ei gynhyrchu gan Mozilla. Ein cenhadaeth yw meithrin Rhyngrwyd iach ac agored.
+Dysgu rhagor
]]>
+
+
+ Preifatrwydd a Diogelwch
+
+
+ Tracio, mewngofnodion, dewisiadau data
+
+
+ Gosod y rhagosodiad, awtogwblhau
+
+
+
+
+ Ynghylch %1$s, cymorth
+
+
+ Diogelwch Uwch Rhag Tracio
+
+
+ Cynnwys Gwe
+
+
+ Newid Apiau
+
+
+ Cyffredinol
+
+
+ Porwr rhagosodedig, iaith
+
+
+ Casglu a Defnydd Data
+
+ Chwilio
+
+
+ Derbyn awgrymiadau chwilio
+
+ Bydd %1$s yn anfon yr hyn rydych yn ei deipio yn y bar cyfeiriad i\'ch peiriant chwilio
+
+
+ Rhagosodiad
+
+
+ Peiriant chwilio
+
+
+ Ymlaen
+
+
+ Diffodd
+
+
+ AwtoGwblhau URL
+
+
+ Am Hoff Wefannau
+
+
+ Galluogi i %s awtogwblhau dros 450 URL poblogaidd yn y bar cyfeiriad.
+
+
+ Eich Gwefannau Chi
+
+
+ Galluogi i %s awtogwblhau eich hoff URLau.
+
+
+ Rheoli gwefannau
+
+
+ Rheoli gwefannau
+
+
+ + Ychwanegu URL cyfaddas
+
+
+ Eich rhestr awtogwblhau:
+
+
+ Ychwanegu URL
+
+
+ Ychwanegu URL cyfaddas
+
+
+ Ychwanegu URL cyfaddas
+
+
+ Ychwanegu dolen i awtogwblhau
+
+
+ Cwcis a Data Gwefan
+
+
+ Dewisiadau Data
+
+
+ Tynnu URLau cyfaddas
+
+
+ Darllen rhagor
+
+
+ Ychwanegu a rheoli URLs awtogwblhau cyfaddas.
+
+
+ URLau i\'w hychwanegu
+
+
+ Gludo neu rhoi URL
+
+
+ Esiampl: mozilla.org
+
+
+ Esiampl: example.com
+
+
+ URL cyfaddas newydd wedi ei ychwanegu.
+
+
+ Tynnu
+
+
+ Tynnu
+
+
+ Gwiriwch yr URL rydych wedi ei roi.
+
+ Iaith
+
+ Rhagosodiad y system
+
+ Preifatrwydd
+ Rhwystro tracwyr hysbysebion
+ Mae rhai hysbysebion yn tracio ymweliadau â gwefannau, hyd yn oed heb i chi eu clicio
+ Rhwystro tracwyr dadansoddi
+ Yn cael eu defnyddio i gasglu, dadansoddi a mesur gweithgareddau fel tapio a sgrolio
+ Rhwystro tracwyr cymdeithasol
+ Wedi eu mewnblannu ar wefannau er mwyn tracio eich ymweliadau ac i ddangos swyddogaethau fel botymau rhannu
+ Rhwystro tracwyr cynnwys eraill
+ Gall ei alluogi achosi i rai tudalennau i ymddwyn yn rhyfedd
+ Rhwystro cwcis
+
+
+ Dim diolch
+ Rhwystro cwcis trydydd parti\'n unig
+ Rhwystro cwcis trydydd parti\'n unig
+
+ Rhwystro cwcis traws-gwefan
+ Iawn!
+
+
+ Defnyddio ôl bys i ddatgloi ap
+
+
+ Datglowch gan ddefnyddio bysbrint os ydych wedi ychwanegu Llwybrau Byr neu pan fo gwefan eisoes ar agor yn %s.
+
+
+ Dirgel
+
+ Cuddio tudalennau gwe wrth newid apiau a rhwystro cymryd lluniau sgrin.
+
+ Diogelwch
+
+ Perfformiad
+ Rhwystro ffontiau Gwe
+
+ Gall olygu colli eiconau neu ddelweddau
+
+ Rhwystro JavaScript
+
+ Gall tudalennau lwytho\'n gynt, ond o bosib ymddwyn yn annisgwyl
+
+
+ Gwneud %1$s yn borwr rhagosodedig
+
+ Mozilla
+ Anfon data defnydd
+
+
+ Darllen rhagor
+
+
+ Mae Mozilla\'n ceisio casglu dim ond yr hyn sydd ei angen arnom i ddarparu a gwella %1$s ar gyfer pawb.
+
+
+ Hysbysiad Preifatrwydd
+
+
+ Manylion trwyddedu
+
+
+ Llyfrgelloedd rydym yn eu defnyddio
+
+
+ %s| Llyfrgelloedd OSS
+
+
+ Ynghylch %1$s
+
+
+ Peiriannau chwilio wedi eu gosod
+
+
+ Dewis peiriant chwilio
+
+
+ Adfer peiriannau chwilio rhagosodedig
+
+
+ + Ychwanegu peiriannau chwilio arall
+ Tynnu peiriannau chwilio
+ Tynnu
+
+
+ Ychwanegu peiriannau chwilio arall
+
+ Dewis eich hoff beiriant:
+
+
+ Ychwanegu peiriant chwilio
+
+ Enw peiriant chwilio
+ Llinyn chwilio i’w ddefnyddio
+ Cadw
+
+
+ Esampl: example.com/search/?q=%s
+
+ Wedi ychwanegu peiriant chwilio.
+
+ Rhowch enw peiriant chwilio
+ Mae peiriant chwilio sydd wedi ei osod eisoes yn defnyddio\'r enw yna.
+
+ Rhowch linyn chwilio
+
+ Gwiriwch fod y llinyn chwilio yn cyfateb i fformat Enghraifft
+
+
+ Clirio\'r mewnbwn
+
+
+ Cau
+
+
+ Dileu hanes pori
+
+
+ Tabiau agored: %1$s
+
+
+ Cysylltiadau diogel
+
+
+ Llwytho
+
+
+ Gwefan wedi ei lwytho
+
+
+ Rhagor o ddewisiadau
+
+
+ Rhagor o fotymau dewis
+
+
+ Symud ymlaen
+
+
+ Ail-lwytho\'r wefan
+
+
+ Symud nôl
+
+
+ Atal llwytho\'r wefan
+
+
+ Nôl i’r ap blaenorol
+
+
+ Nifer o dracwyr wedi eu rhwystro
+
+
+ Rhwystro tracwyr
+
+ Eich Hawliau
+
+ Agor dolen mewn ap arall
+
+ Gallwch adael i %1$s agor y ddolen yn %2$s.
+
+ Canfod ap sy\'n gallu agor dolen
+
+ 0oes dim o\'r apiau ar eich dyfais yn gallu i agor y ddolen. Mae modd gofyn i %1$s chwilio %2$s am ap sy\' n gallu gwneud.
+
+ Gadael Pori Preifat?
+
+
+ %1$s wedi gorffen
+
+
+ Agor
+
+
+
+
+
+
+
+
+
+
+ Ychwanegwyd at y llwybrau byr!
+
+ Heb ganfod gweinydd
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cau
+
+
+
+ Croeso i %1$s
+
+
+ Cyflym. Preifat. Dim tarfu.
+
+
+ Cychwyn arni
+
+
+
+ Nid yw %1$s yn debyg i borwyr eraill
+
+
+
+ Rydyn ni’n dileu eich hanes pan fyddwch chi’n cau’r ap er mwyn preifatrwydd ychwanegol.
+
+
+
+ Gwnewch %1$s eich porwr rhagosodedig i ddiogelu eich data gyda phob dolen rydych yn eu hagor.
+
+
+ Gosod fel y porwr ragosodedig
+
+
+ Hepgor
+
+
+
+ Cryfhewch eich preifatrwydd
+
+ Cymrwch bori preifat i\'r lefel nesaf. Rhwystrwch hysbysebion a chynnwys arall sy\'n gallu\'ch tracio ar draws wefannau ac arafu amserau llwytho tudalennau.
+
+
+ Chwilio, cynhwysfawr
+
+ Chwilio am rywbeth gwahanol? Dewiswch beiriant chwilio arall yn y Gosodiadau.
+
+
+ Ychwanegu llwybrau byr i\'ch sgrin cartref
+
+ Ewch i\'ch hoff wefannau yn %1$s yn gyflym. Dewiswch \"Ychwanegu i\'r Sgrin Cartref\" o ddewislen %1$s.
+
+
+ Arfer preifatrwydd
+
+ Gosodwch %1$s fel eich porwr ragosodedig a derbyn buddiannau pori preifat wrth agor tudalennau gwe mewn apiau eraill.
+
+ Iawn!
+ Hepgor
+ Nesaf
+
+
+ -
+
+
+ Ychwanegu
+
+
+ IAWN
+
+
+ Diddymu
+
+
+ NA
+
+
+ Bydd y Llwybr Byr yn agor gyda Diogelwch rhag Tracio wedi ei analluogi
+
+
+ Sesiwn pori preifat
+
+
+ Mae hysbysiadau\'n caniatáu i chi ddileu eich sesiwn %1$s gyda thap. Does dim angen agor yr ap na gweld beth sy\'n rhedeg yn eich porwr.
+
+
+ Dileu hanes pori
+
+
+ Llwytho Firefox i Lawr
+
+
+
+
+
+
+
+
+ Trwydded Gyhoeddus Mozilla a thrwyddedau cod agored eraill.]]>
+
+
+ yma.]]>
+
+
+ drwyddedau rhydd a chod agored.]]>
+
+
+ Trwydded Gyhoeddus Gyffredinol GNU v3, ac sydd ar gael yma .]]>
+
+
+ Enw Defnyddiwr
+ Cyfrinair
+ Clirio
+
+
+
+ Mae’r Cysylltiad yn Ddiogel
+ Cysylltiad Anniogel
+
+ Dilyswyd gan: %1$s
+
+
+ Diogelwch Gwefan
+ URL eisoes yn bod
+
+
+ Canfod ar y Dudalen
+
+
+ Canfod ar y dudalen
+
+
+ %1$d/%2$d
+
+ %1$d o %2$d
+
+
+ Canfod y canlyniad nesaf
+
+ Canfod y canlyniad blaenorol
+
+ Cau’r canfod ar y dudalen
+
+
+
+
+ Gwefan bwrdd gwaith
+
+
+ Gwefan bwrdd gwaith
+
+
+ URL wedi’i gopïo
+
+
+ Offer datblygwyr
+
+
+ Agor dolenni mewn apiau
+
+
+ Uwch
+
+
+ Caniatâd gwefan
+
+
+ Cyfyngu Baneri Cwcis
+
+
+ Ymlaen
+
+
+ Diffodd
+
+
+ Cyfyngu Baneri Cwcis
+
+
+ Gweld llai o faneri trwy wrthod ceisiadau cwcis yn awtomatig, pan fo modd.
+
+ -->
+ Cyfyngu Baneri Cwcis
+
+
+ YMLAEN ar gyfer y wefan hon
+
+
+ Nid yw’r wefan yn cael ei chefnogi ar hyn o bryd
+
+
+ DIFFODD ar gyfer y wefan hon
+
+
+ Cyfyngu Baneri Cwcis
+
+
+ DIFFODD ar gyfer y wefan hon
+
+
+ YMLAEN ar gyfer y wefan hon
+
+
+ Troi Llai o Faneri Cwcis ymlaen ar %1$s?
+
+
+ Diffodd Llai o Faneri Cwcis ar %1$s?
+
+
+ Bydd %1$s yn clirio cwcis y wefan hon ac yn adnewyddu’r dudalen. Gall clirio pob cwci eich allgofnodi neu wagio eich certiau siopa.
+
+
+ Gall %1$s geisio gwrthod ceisiadau cwcis yn awtomatig.
+
+
+ Nid yw’r wefan hon yn cael ei chefnogi ar hyn o bryd gan Llai o Faneri Cwcis. Hoffech chi ofyn i’n tîm adolygu’r wefan hon ac ychwanegu cefnogaeth yn y dyfodol?
+
+
+ Diddymu
+
+
+ Gofyn am gymorth
+
+
+ Cais i’r wefan cymorth wedi’i gyflwyno.
+
+
+ Cais i’r wefan cymorth wedi’i gyflwyno.
+
+
+
+ Mae %1$s yn ceisio gwrthod ceisiadau cwci i gau baneri cwci annifyr.\n\nRheolwch ddewisiadau baneri cwci yn y %2$s.
+
+
+ gosodiadau
+
+
+ Awtochwarae
+
+
+ I’w ganiatáu:
+
+
+ 1. Ewch i Gosodiadau Android
+
+
+ Caniatâd]]>
+
+
+ Mynd i’r gosodiadau
+
+
+ %1$s i YMLAEN]]>
+
+
+ Camera
+
+
+ Meicroffon
+
+
+ Lleoliad
+
+
+ Hysbysiad
+
+
+ Cynnwys wedi’i reoli gan DRM
+
+
+ Gofyn i ganiatáu
+
+
+ Rhwystrwyd
+
+
+ Caniatawyd
+
+
+ Rhwystrwyd gan Android
+
+
+ Caniatáu sain a fideo
+
+
+ Rhwystro sain yn unig
+
+
+ Cymeradwy
+
+
+ Rhwystro sain a fideo
+
+
+ Astudiaethau
+
+
+ Efallai y bydd Firefox yn gosod a rhedeg astudiaethau o bryd i’w gilydd.
+
+
+ Darllen rhagor
+
+
+ Bydd yr ap yn cau er mwyn gosod y newidiadau
+
+
+ Tynnu
+
+
+ Gweithredol
+
+
+ Cwblhawyd
+
+
+ Dadfygio pell drwy\'r USB/Diwifr
+
+
+ Datgloi
+
+
+ Cadarnhau Defnyddio Eich Bysbrint
+
+
+ Gallwch ddefnyddio’ch bysbrint i barhau â’ch sesiwn ap gyfredol.
+
+
+ Agor Dolen mewn Sesiwn Newydd
+
+
+ Eicon ôl bys
+
+
+ Ôl bys heb ei adnabod. Ceisiwch eto.
+
+
+ Symudodd y bys yn rhy gyflym. Ceisiwch eto.
+
+
+ Dangos awgrymiadau chwilio?
+
+
+ I dderbyn awgrymiadau, mae %1$s angen anfon yr hyn rydych chi’n ei deipio yn y bar cyfeiriad i’r peiriant chwilio.
+
+
+ Na
+
+
+ Iawn
+
+
+ Nid yw rhai peiriannau chwilio\'n gallu dangos awgrymiadau.
+
+
+ Cau
+
+
+
+
+ Gwefan yn ymddwyn yn anisgwyl?\n Ceisiwch ddiffodd Diogelwch rhag Tracio
+
+
+ Ychwanegu i\'r Dudalen Cartref]]>
+
+
+ Agor pob dolen yn %1$s\n Gosod %1$s fel y porwr rhagosodedig
+
+
+ Gosod URLau awtogwblhau i\'ch gwefannau gorau\n Pwyso\'n hir unrhyw URL yn y bar cyfeiriad
+
+
+ Agor dolen mewn tab newydd\n Pwyso\'n hir ar unrhyw ddolen ar dudalen
+
+
+ Diffodd awgrymiadau ar y sgrin cychwyn
+
+
+ Tab newydd wedi ei agor
+
+
+ Newid
+
+
+ Mynd i’r sgrin lawn
+
+
+ Newid i ddolen mewn tab newydd ar unwaith
+
+
+ Rhwystro gwefannau peryglus a thwyllodrus posib
+
+ Rhwystro gwefannau twyllodrys ac ymosodol, rhai drwgwar, a meddalwedd diangen hysbys.
+
+
+ Modd HTTPS-yn-Unig
+
+
+ Yn ceisio cysylltu’n awtomatig â gwefannau gan ddefnyddio’r protocol amgryptio HTTPS am fwy o ddiogelwch.
+
+
+ Eithriadau
+
+ Rydych chi wedi analluogi Rhwystro Cynnwys y gwefannau hyn.
+
+ Tynnu
+
+ Tynnu pob gwefan
+
+
+ Rhwystro Cwcis
+
+
+ Hoffech chi rwystro cwcis?
+
+
+ Tab wedi Chwalu
+
+ Ymddiheuriadau. Rydym yn cael anhawster gyda\'r tab hwn.
+
+ Fel porwr preifat, fyddwn ni byth yn cadw ac felly\'n methu adfer y tab hwn.
+
+ Cau Tab
+
+
+
+
+
+ Anfon adroddiad chwalu at Mozilla
+
+
+
+
+ Tracwyr wedi’u rhwystro ers %s
+
+ Cynnwys
+
+ Hysbysebu
+
+ Cymdeithasol
+
+ Dadansoddeg
+
+ Diogelwch Uwch Rhag Tracio
+
+ Mae diogelu I FFWRDD ar gyfer y wefan hon
+
+ Mae diogelu YMLAEN ar gyfer y wefan hon
+
+ Cysylltiad yn ddiogel
+
+ Nid yw’r cysylltiad yn ddiogel
+
+ Tracwyr a Sriptiau i’w Rhwystro
+
+
+ Mynd nôl
+
+
+
+ Tynnu
+
+
+ Ailenwi
+
+ Ailenwi
+
+
+ Enw llwybr byr
+
+
+ Fydd delweddau sydd wedi eu cadw a’u rhannu <b>ddim yn cael eu dileu</b> pan fyddwch yn dileu hanes %1$s.
+
+
+
+ Thema
+
+ Golau
+
+ Tywyll
+
+ Gosodwyd gan yr Arbedwr Batri
+
+ Dilyn thema’r ddyfais
+
+
+
+ Nid yw’r wefan hon yn cefnogi HTTPS
+
+
+ Dysgu rhagor
+ Newidiwch y gosodiad hwn yn Gosodiadau > Preifatrwydd a Diogelwch > Diogelwch.]]>
+
+
+ Nid yw’r cysylltiad yn ddiogel
+
+
+
+ Os ydych wedi cysylltu â’r gweinydd hwn yn llwyddiannus yn y gorffennol, gall y gwall fod yn un dros dro.
+ ]]>
+
+
+ Gall rhywun fod yn ceisio dynwared y wefan a gall mynd ymlaen fod yn beryglus.
+
+ Nid yw %1$s yn ymddiried yn %2$s oherwydd bod cyhoeddwr ei dystysgrif yn anhysbys, maer dystysgrif wedi’i hunan-lofnodi, neu nid yw’r gweinydd yn anfon y tystysgrifau canolradd cywir.
+ ]]>
+
+
+
+ Cau tab
+
+
+
+ Wedi’u dal! Rydym wedi rhwystro’r wefan hon rhag ysbïo arnoch chi. Tapiwch y darian i gael gwybodaeth am yr hyn rydyn ni’n eu rhwystro.
+
+
+ Cau’r llamlen
+
+
+
+ Rydych wedi’ch diogelu!
+
+ Mae’r gosodiadau rhagosodedig hyn yn cynnig diogelwch cryf. Ond mae’n hawdd newid y gosodiadau i ddiwallu’ch anghenion penodol.
+
+ Cau
+
+
+ Tapiwch yma i roi’r cyfan yn y bin sbwriel - hanes, cwcis, popeth - a dechrau’n ffres ar dab newydd.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Cau
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Chwilio teclyn
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Hanes pori wedi’i glirio! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Cychwynnwch eich sesiwn bori breifat, a byddwn yn rhwystro tracwyr a phethau drwg eraill wrth i chi fynd ymlaen.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Byddwn yn eich gadael i’ch pori preifat, ond yn cael cychwyn cyflymach y tro nesaf gyda’r teclyn %1$s ar eich sgrin Cartref.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Ychwanegu teclyn i’r sgrin cartref
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Teclyn wedi’i ychwanegu i’r sgrin cartref
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-da/strings.xml b/mobile/android/focus-android/app/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000000..f2872a655b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-da/strings.xml
@@ -0,0 +1,1122 @@
+
+
+
+
+
+
+
+
+ Annuller
+
+ OK
+
+ Gem
+
+
+ Søg eller indtast adresse
+
+ Automatisk privat browsing.\nSurf. Slet. Start forfra.
+
+
+ Din browser-historik er blevet slettet.
+
+ Browserhistorik blev ryddet
+
+
+ Fanebladets historik er slettet.
+
+
+ Søg efter %1$s
+
+
+ Del…
+
+
+ Rapportér problem med websted
+
+
+ Åbn i %1$s
+
+
+ Åbn i…
+
+
+ Føj til startskærmen
+
+
+ Føj til genveje
+
+ Fjern fra genveje
+
+
+ Indstillinger
+ Om
+ Hjælp
+ Dine rettigheder
+
+
+ Forsøg på sporing blokeret
+
+
+ Deaktivering af dette kan afhjælpe visse webstedsproblemer
+
+
+ Blokering af indhold
+
+ Deaktiver for at løse problemer på visse websteder
+
+
+ %1$s
+
+
+ Del via
+
+ Slet browser-historik?
+
+ Tryk på eller ryd denne meddelelse for at slette din browser-historik på en sikker måde.
+
+
+ Tryk på eller stryg denne meddelelse for at slette din browser-historik på en sikker måde.
+
+ Slet browser-historik
+
+
+ Åbn
+
+
+ Slet og åbn
+
+
+ Slet
+
+
+ Slet browser-historik
+
+
+
+ Slet og åbn
+
+
+ Slet og åbn %1$s
+
+
+
+ Søg i Focus
+
+ Søg i Klar
+
+ Søg i Focus Beta
+
+ Søg i Focus Nightly
+
+
+ %1$s giver dig fuld kontrol.
+Anvend den som privat browser:
+
+ Søg og surf direkte i appen
+ Bloker sporing (eller opdater indstillingerne og tillad sporing)
+ Fjern nemt cookies samt søge- og browser-historik
+
+
+%1$s er udviklet af Mozilla. Vores formål er at fremme et sundt, åbent internet.
+Læs mere
]]>
+
+
+ Privatliv og sikkerhed
+
+
+ Sporing, cookies, datadeling
+
+
+ Angiv standard, autofuldførelse
+
+
+
+
+ Om %1$s, hjælp
+
+
+ Udvidet beskyttelse mod sporing
+
+
+ Webindhold
+
+
+ Skifte mellem apps
+
+
+ Generelt
+
+
+ Standard-browser, sprog
+
+
+ Dataindsamling og brug
+
+ Søgning
+
+
+ Vis søgeforslag
+
+ %1$s sender det indtastede i adresselinjen til den valgte søgetjeneste
+
+
+ Standard
+
+
+ Søgetjeneste
+
+
+ Aktiveret
+
+
+ Deaktiveret
+
+
+ Autofuldfør URL
+
+
+ For populære websteder
+
+
+ Slå til for at få %s til at autofuldføre over 450 populære URLer i adressefeltet.
+
+
+ For websteder du tilføjer
+
+
+ Slå til for at få %s til at autofuldføre dine foretrukne URLer.
+
+
+ Administrer websteder
+
+
+ Administrer websteder
+
+
+ + Tilføj brugerdefineret URL
+
+
+ Din autofuldførelses-liste:
+
+
+ Tilføj URL
+
+
+ Tilføj brugerdefineret URL
+
+
+ Tilføj brugerdefineret URL
+
+
+ Tilføj link til autofuldførelse
+
+
+ Cookies og websteds-data
+
+
+ Datadeling
+
+
+ Fjern brugerdefinerede URL\'er
+
+
+ Læs mere
+
+
+ Tilføj og håndter brugerdefineret autofuldførelse af URL\'er.
+
+
+ URL der skal tilføjes
+
+
+ Indsæt eller skriv URL
+
+
+ Eksempel: mozilla.org
+
+
+ Eksempel: eksempel.dk
+
+
+ Ny brugerdefineret URL tilføjet.
+
+
+ Fjern
+
+
+ Fjern
+
+
+ Kontroller den indtastede URL.
+
+ Sprog
+
+ Systemstandard
+
+ Privatliv
+ Bloker sporing via reklamer
+ Nogle reklamer sporer dine besøg på websteder, selvom du ikke klikker på dem
+ Bloker analytisk sporing
+ Bruges til at indsamle, analysere og måle aktiviteter som tryk og scrolling
+ Bloker sporing via sociale medier
+ Indlejret på websteder for at spore dine besøg og vise funktionalitet som dele-knapper
+ Bloker andre former for indholdssporing
+ Aktivering kan medføre, at visse sider opfører sig uventet
+ Bloker cookies
+
+
+ Nej tak
+ Bloker kun sporings-cookies fra tredjepart
+ Bloker kun cookies fra tredjepart
+
+ Bloker cookies på tværs af websteder
+ Ja tak
+
+
+ Brug fingeraftryk til at låse appen op
+
+
+ Lås op med dit fingeraftryk, hvis du har tilføjet genveje eller når et websted allerede er åbent i %s.
+
+
+ Usynlig
+
+ Skjul indhold på listen over de seneste brugte apps, og undgå at der tages skærmbilleder.
+
+ Sikkerhed
+
+ Ydelse
+ Bloker webskrifttyper
+
+ Kan medføre at ikoner eller billeder mangler
+
+ Bloker JavaScript
+
+ Sider indlæses muligvis hurtigere, men kan også opføre sig uventet
+
+
+ Angiv %1$s som standard-browser
+
+ Mozilla
+ Send data for anvendelse
+
+
+ Læs mere
+
+
+ Mozilla tilstræber kun at indsamle, hvad vi skal bruge for at kunne udbyde og forbedre %1$s for alle.
+
+
+ Privatlivserklæring
+
+
+ Licensinformation
+
+
+ Biblioteker, som vi bruger
+
+
+ %s | OSS-biblioteker
+
+
+ Om %1$s
+
+
+ Installerede søgetjenester
+
+
+ Vælg søgetjeneste
+
+
+ Gendan standard-søgetjenester
+
+
+ + Tilføj en ny søgetjeneste
+ Fjern søgetjenester
+ Fjern
+
+
+ Tilføj en ny søgetjeneste
+
+ Vælg din foretrukne søgetjeneste:
+
+
+ Tilføj søgetjeneste
+
+ Søgetjenestens navn
+ Søgestreng der skal anvendes
+ Gem
+
+
+ Eksempel: eksempel.dk/search/?q=%s
+
+ Ny søgetjeneste tilføjet.
+
+ Indtast søgetjenestens navn
+ En installeret søgetjeneste bruger allerede dette navn.
+
+ Indtast søgestreng
+
+ Kontroller at søgestrengens format svarer til eksemplets
+
+
+ Ryd adressefeltet
+
+
+ Afvis
+
+
+ Slet browser-historik
+
+
+ Åbne faneblade: %1$s
+
+
+ Sikker forbindelse
+
+
+ Siden indlæses
+
+
+ Siden er indlæst
+
+
+ Flere indstillinger
+
+
+ Knap til flere muligheder
+
+
+ Gå frem
+
+
+ Genindlæs siden
+
+
+ Gå tilbage
+
+
+ Afbryd indlæsning af siden
+
+
+ Tilbage til forrige app
+
+
+ Antal blokerede sporings-tjenester
+
+
+ Bloker sporing
+
+ Dine rettigheder
+
+ Åbn link i en anden app
+
+ Du kan forlade %1$s og åbne dette link i %2$s.
+
+ Find en app der kan åbne linket
+
+ Ingen app på din enhed kan åbne dette link. Du kan forlade %1$s og søge på %2$s efter en app der kan.
+
+ Afslut privat browsing?
+
+
+ %1$s hentet
+
+
+ Åbn
+
+
+
+
+
+
+
+
+
+
+ Føjet til genveje!
+
+ Serveren blev ikke fundet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Luk
+
+
+
+ Velkommen til %1$s
+
+
+ Hurtig. Privat. Ingen distraktioner.
+
+
+ Kom i gang
+
+
+
+ %1$s er ikke som andre browsere
+
+
+ Vi rydder din historik for ekstra privatlivsbeskyttelse, når du lukker appen.
+
+
+
+ Gør %1$s til din standard-browser for at beskytte dine data, hver gang du åbner et link.
+
+
+ Angiv som standard-browser
+
+
+ Spring over
+
+
+
+ Styrk dit privatliv
+
+ Tag privat browsing til næste niveau. Bloker reklamer og andet indhold, der kan spore din færden på nettet og gøre det langsommere at hente sider.
+
+
+ Søgning på dine præmisser
+
+ Leder du efter noget andet? Vælg en ny standard-søgetjeneste i Indstillinger.
+
+
+ Tilføj genveje til din startskærm
+
+ Gå hurtigt tilbage til dine yndlingssider i %1$s. Vælg blot \"Føj til startskærmen\" fra menuen i %1$s.
+
+
+ Gør beskyttelse af dit privatliv til en vane
+
+ Angiv %1$s som din standard-browser og få fordelene ved privat browsing, når du åbner websider fra andre apps.
+
+ OK, forstået!
+ Spring over
+ Næste
+
+
+ -
+
+
+ Tilføj
+
+
+ JA
+
+
+ Annuller
+
+
+ NEJ
+
+
+ Når genvejen åbnes vil Udvidet Sporingsbeskyttelse være slået fra
+
+
+ Privat browsing-session
+
+
+ Med beskeder kan du slette din %1$s-session med et enkelt tryk. Du behøver hverken åbne appen eller se, hvad der kører i din browser.
+
+
+ Slet browser-historik
+
+
+ Hent Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License og andre licenser til åben kildekode.]]>
+
+
+ her.]]>
+
+
+ licenser.]]>
+
+
+ GNU General Public License v3. Listerne er tilgængelige her .]]>
+
+
+ Brugernavn
+ Adgangskode
+ Ryd
+
+
+
+ Sikker forbindelse
+ Usikker forbindelse
+
+ Bekræftet af: %1$s
+
+
+ Sikkerhed for webstedet
+ URL\'en findes allerede
+
+
+ Find på side
+
+
+ Find på siden
+
+
+ %1$d/%2$d
+
+ %1$d af %2$d
+
+
+ Find næste
+
+ Find foregående
+
+ Deaktiver find på siden
+
+
+
+
+ Bed om desktop-version
+
+
+ Desktop-version
+
+
+ URL kopieret
+
+
+ Udviklerværktøj
+
+
+ Åbn links i apps
+
+
+ Avanceret
+
+
+ Websteds-indstillinger
+
+
+ Reduktion af cookie-bannere
+
+
+ Til
+
+
+ Fra
+
+
+ Reduktion af cookie-bannere
+
+
+ Se færre bannere ved automatisk at afvise cookie-anmodninger, når det er muligt.
+
+ -->
+ Reduktion af cookie-bannere
+
+
+ Slået TIL for dette websted
+
+
+ Webstedet understøttes ikke i øjeblikket
+
+
+ Slået FRA for dette websted
+
+
+ Reduktion af cookie-bannere
+
+
+ Slået FRA for dette websted
+
+
+ Slået TIL for dette websted
+
+
+ Vil du slå reduktion af cookie-bannere til for %1$s?
+
+
+ Vil du slå reduktion af cookie-bannere fra for %1$s?
+
+
+ %1$s vil rydde dette websteds cookies og genindlæse siden. Rydning af alle cookies kan logge dig ud eller tømme indkøbskurve.
+
+
+ %1$s kan forsøge at afvise cookie-anmodninger automatisk.
+
+
+ Dette websted understøttes i øjeblikket ikke af reduktion af cookie-bannere. Vil du anmode vores team om at gennemgå dette websted for at det kan understøttes i fremtiden?
+
+
+ Annuller
+
+
+ Anmod om understøttelse
+
+
+ Anmodning om understøttelse af websted indsendt.
+
+
+ Anmodning om understøttelse af websted indsendt.
+
+
+
+ %1$s prøver på at afvise cookie-anmodninger for at lukke irriterende cookie-bannere.\n\nHåndter indstillinger for cookie-bannere i %2$s.
+
+ Indstillingerne
+
+
+ Automatisk afspilning
+
+
+ For at tillade:
+
+
+ 1. Gå til Indstillinger i Android
+
+
+ Tilladelser]]>
+
+
+ Gå til indstillinger
+
+
+ %1$s TIL]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Placering
+
+
+ Meddelelser
+
+
+ DRM-kontrolleret indhold
+
+
+ Spørg om tilladelse
+
+
+ Blokeret
+
+
+ Tilladt
+
+
+ Blokeret af Android
+
+
+ Tillad lyd og video
+
+
+ Bloker kun lyd
+
+
+ Anbefalet
+
+
+ Bloker lyd og video
+
+
+ Undersøgelser
+
+
+ Firefox kan installere og afvikle undersøgelser.
+
+
+ Læs mere
+
+
+ Appen afsluttes for at anvende ændringerne
+
+
+ Fjern
+
+
+ Aktive
+
+
+ Gennemførte
+
+
+ Ekstern debugging via USB/wi-fi
+
+
+ Lås op
+
+
+ Bekræft ved hjælp af dit fingeraftryk
+
+
+ Du kan bruge dit fingeraftryk til at fortsætte din aktuelle app-session.
+
+
+ Åbn link i en ny session
+
+
+ Fingeraftryks-ikon
+
+
+ Fingeraftryk ikke genkendt. Prøv igen.
+
+
+ Fingeren bevægede sig for hurtigt. Prøv igen.
+
+
+ Vis søgeforslag?
+
+
+ For at kunne vise søgeforslag sender %1$s dine indtastninger i søgefeltet til søgetjenesten.
+
+
+ Nej
+
+
+ Ja
+
+
+ Nogle søgetjenester kan ikke vise forslag.
+
+
+ Luk
+
+
+
+
+ Opfører siden sig uventet?\n Prøv at deaktivere beskyttelse mod sporing
+
+
+ Føj til startskærmen]]>
+
+
+ Åbn alle links i %1$s\n Angiv %1$s som standard-browser
+
+
+ Autofuldfør URL\'er for de sider, du bruger mest\n Tryk og hold en URL i adressefeltet
+
+
+ Åbn link i et nyt faneblad\n Tryk og hold links på en side
+
+
+ Slå tips på startskærmen fra
+
+
+ Nyt faneblad er åbnet
+
+
+ Skift
+
+
+ Fuldskærmstilstand aktiveret
+
+
+ Skift straks til link i nyt faneblad
+
+
+ Bloker potentielt farlige og vildledende websteder
+
+ Bloker sider, der er indrapporteret som vildledende, farlige, indeholdende malware eller uønsket software.
+
+
+ Tilstanden Kun-HTTPS
+
+
+ Forsøger automatisk at oprette forbindelse til websteder ved hjælp af krypteringsprotokollen HTTPS for øget sikkerhed.
+
+
+ Undtagelser
+
+ Du har deaktiveret blokering af indhold for disse websteder.
+
+ Fjern
+
+ Fjern alle websteder
+
+
+ Bloker cookies
+
+
+ Vil du blokere cookies?
+
+
+ Fanebladet gik ned
+
+ Beklager, vi har et problem med denne fane.
+
+ Som en privat browser gemmer vi aldrig, så vi kan ikke gendanne dette faneblad.
+
+ Luk faneblad
+
+
+
+
+
+ Send fejlrapport til Mozilla
+
+
+
+
+ Blokerede sporings-mekanismer blokeret siden %s
+
+ Indhold
+
+ Reklamer
+
+ Social
+
+ Analyse
+
+ Udvidet beskyttelse mod sporing
+
+ Beskyttelse er slået FRA for dette websted
+
+ Beskyttelse er slået TIL for dette websted
+
+
+ Forbindelsen er sikker
+
+ Forbindelsen er ikke sikker
+
+ Sporings-mekanismer og scripts, der skal blokeres
+
+
+ Gå tilbage
+
+
+
+ Fjern
+
+
+ Omdøb
+
+ Omdøb
+
+
+ Genvejsnavn
+
+
+ Gemte og delte billeder <b>vil ikke blive slettet,</b> når du sletter historikken i %1$s
+
+
+
+ Tema
+
+ Lyst
+
+ Mørkt
+
+ Indstil med strømstyring
+
+ Samme som enhedens tema
+
+
+
+ Dette websted understøtter ikke HTTPS
+
+
+ Læs mere
+ Skift denne indstilling i Indstillinger > Privatliv og sikkerhed > Sikkerhed.]]>
+
+
+ Forbindelsen er ikke sikker
+
+
+
+ Hvis du har kunnet tilgå serveren tidligere, kan dette problem være midlertidigt.
+ ]]>
+
+
+ Nogen kan have lavet en falsk version af webstedet, og det kan være risikabelt at fortsætte.
+
+ %1$s stoler ikke på %2$s , fordi udstederen af webstedets certifikat er ukendt, fordi certifikatet er underskrevet af indehaveren selv, eller fordi serveren ikke sender de korrekte mellemliggende certifikater.
+ ]]>
+
+
+
+ Luk faneblad
+
+
+
+ Taget på fersk gerning! Vi forhindrede dette websted i at udspionere dig. Tryk på skjoldet når som helst for at se, hvad vi blokerer.
+
+
+ Luk pop op
+
+
+
+ Du er beskyttet!
+
+ Disse standard-indstillinger giver stærk beskyttelse. Men det er nemt at tilpasse indstillingerne, så de opfylder dine specifikke behov.
+
+ Afvis
+
+
+ Tryk her for at slette det hele — historik, cookies, alt — og start på en frisk i et nyt faneblad.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Luk
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Søge-widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Browserhistorik blev ryddet! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start din private browsing-session - vi blokerer sporings-mekanismer og andet skidt.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Vi overlader dig til din private browsing, men få en hurtigere start næste gang med %1$s-widget\'en på din startskærm.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Føj widget til startskærmen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget føjet til startskærmen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-de/strings.xml b/mobile/android/focus-android/app/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000000..4610dab3c2
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,1120 @@
+
+
+
+
+
+
+
+
+ Abbrechen
+
+ OK
+
+ Speichern
+
+
+ Suchen oder Adresse eingeben
+
+ Automatischer Privater Modus.\nSurfen. Löschen. Wiederholen.
+
+
+ Die Surf-Chronik wurde gelöscht.
+ Surf-Chronik gelöscht
+
+
+ Die Surf-Chronik des Tabs wurde gelöscht.
+
+
+ Suchen nach %1$s
+
+
+ Teilen …
+
+
+ Seitenproblem melden
+
+
+ In %1$s öffnen
+
+
+ Öffnen in…
+
+
+ Zum Startbildschirm hinzufügen
+
+
+ Zu Verknüpfungen hinzufügen
+
+ Aus Verknüpfungen entfernen
+
+
+ Einstellungen
+ Über
+ Hilfe
+ Ihre Rechte
+
+
+ Verfolger blockiert
+
+
+ Wenn Sie diese Option deaktivieren, werden möglicherweise einige Probleme mit der Website behoben
+
+
+ Inhaltsblockierung
+
+ Deaktivieren, um Probleme mit manchen Websites zu lösen
+
+
+ Bereitgestellt von %1$s
+
+
+ Teilen über
+
+ Browser-Chronik löschen?
+ Tippen oder löschen Sie diese Benachrichtigung, um Ihre Browser-Chronik sicher zu löschen.
+
+
+ Tippen oder wischen Sie diese Benachrichtigung, um Ihre Browser-Chronik sicher zu löschen.
+
+ Browser-Chronik löschen
+
+
+ Öffnen
+
+
+ Löschen und öffnen
+
+
+ Löschen
+
+
+ Browser-Chronik löschen
+
+
+
+ Löschen & Öffnen
+
+
+ Löschen und %1$s öffnen
+
+
+
+ In Focus suchen
+
+ In Klar suchen
+
+ In Focus Beta suchen
+
+ In Focus Nightly suchen
+
+
+ %1$s gibt Ihnen die Kontrolle.
+Verwenden Sie ihn als privaten Browser:
+
+ Suchen und blättern Sie in der App
+ Blockieren Sie Tracker (oder aktualisieren Sie die Einstellungen, um Tracker zu erlauben)
+ Entfernen Sie Cookies, um Such- und Browserverlauf zu löschen
+
+
+%1$s wird von Mozilla entwickelt. Unsere Mission ist es, ein gesundes, offenes Internet zu fördern.
+Erfahren Sie mehr
]]>
+
+
+ Datenschutz & Sicherheit
+
+
+ Aktivitätenverfolgung, Cookies, Datenübermittlung
+
+
+ Als Standard setzen, automatisch vervollständigen
+
+
+
+
+ Über %1$s, Hilfe
+
+
+ Verbesserter Tracking-Schutz
+
+
+ Web-Inhalt
+
+
+ Apps wechseln
+
+
+ Allgemein
+
+
+ Standardbrowser, Sprache
+
+
+ Sammlung und Nutzung von Daten
+
+ Suche
+
+
+ Suchvorschläge erhalten
+
+ %1$s übermittelt Ihre Eingaben in die Adressleiste an Ihre Suchmaschine
+
+
+ Standard
+
+
+ Suchmaschine
+
+
+ An
+
+
+ Aus
+
+
+ Adressen-Autovervollständigung
+
+
+ Für meistbesuchte Seiten
+
+
+ Aktivieren Sie diese Option, damit %s über 450 beliebte Adressen in der Adressleiste automatisch vervollständigt.
+
+
+ Für Websites, die Sie hinzufügen
+
+
+ Aktivieren Sie diese Option, damit %s Ihre Lieblingsadressen automatisch vervollständigt.
+
+
+ Websites verwalten
+
+
+ Websites verwalten
+
+
+ + Benutzerdefinierte Adresse hinzufügen
+
+
+ Ihre Autovervollständigungsliste:
+
+
+ URL hinzufügen
+
+
+ Benutzerdefinierte Adresse hinzufügen
+
+
+ Benutzerdefinierte Adresse hinzufügen
+
+
+ Link zum Autovervollständigen hinzufügen
+
+
+ Cookies und Websitedaten
+
+
+ Datenübermittlung
+
+
+ Benutzerdefinierte Adressen entfernen
+
+
+ Weitere Informationen
+
+
+ Benutzerdefinierte Adressen für Autovervollständigung hinzufügen und verwalten.
+
+
+ Hinzuzufügende Adresse
+
+
+ Adresse eingeben oder einfügen
+
+
+ Beispiel: mozilla.org
+
+
+ Beispiel: example.com
+
+
+ Neue benutzerdefinierte Adresse hinzugefügt.
+
+
+ Entfernen
+
+
+ Entfernen
+
+
+ Überprüfen Sie die eingegebene Adresse.
+
+ Sprache
+
+ Systemstandard
+
+ Datenschutz
+ Werbeverfolgung blockieren
+ Manche Werbeanzeigen verfolgen Ihre Seitenbesuche, auch wenn Sie nicht auf die Anzeigen klicken
+ Analyseverfolgung blockieren
+ Damit werden Aktivitäten wie Tippen und Blättern gesammelt, analysiert und ausgewertet
+ Verfolgung durch soziale Netzwerke blockieren
+ Auf Websites eingebettet, um Ihre Besuche zu verfolgen und Funktionen wie Links zum Teilen anzuzeigen
+ Andere Inhaltsverfolgung blockieren
+ Dies kann dazu führen, dass manche Seiten sich ungewöhnlich verhalten
+ Cookies blockieren
+
+
+ Nein, danke
+ Nur Tracker-Cookies von Drittanbietern blockieren
+ Nur Cookies von Drittanbietern blockieren
+
+ Seitenübergreifende Cookies blockieren
+ Ja, bitte
+
+
+ Fingerabdruck zum Entsperren der App verwenden
+
+
+ Mit Fingerabdruck entsperren, wenn Sie Verknüpfungen hinzugefügt haben oder wenn eine Website bereits in %s geöffnet ist.
+
+
+ Tarnung
+
+ Webseiten beim Wechseln von Apps verstecken und Aufnahme von Bildschirmfotos blockieren.
+
+ Sicherheit
+
+ Leistung
+ Web-Schriftarten blockieren
+
+ Kann zu fehlenden Symbolen oder Grafiken führen
+
+ JavaScript blockieren
+
+ Seiten laden schneller, verhalten sich aber möglicherweise unerwartet
+
+
+ %1$s als Standardbrowser festlegen
+
+ Mozilla
+ Nutzungsdaten senden
+
+
+ Mehr erfahren
+
+
+ Mozilla ist bestrebt, nur die Informationen zu sammeln, mit denen wir %1$s anbieten und für alle Nutzer verbessern können.
+
+
+ Datenschutzhinweis
+
+
+ Informationen zur Lizenzierung
+
+
+ Von uns verwendete Bibliotheken
+
+
+ %s | OSS-Bibliotheken
+
+
+ Über %1$s
+
+
+ Installierte Suchmaschinen
+
+
+ Suchmaschine auswählen
+
+
+ Standardsuchmaschinen wiederherstellen
+
+
+ + Weitere Suchmaschine hinzufügen
+ Suchmaschinen entfernen
+ Entfernen
+
+
+ Weitere Suchmaschine hinzufügen
+
+ Wählen Sie Ihre bevorzugte Suchmaschine:
+
+
+ Suchmaschine hinzufügen
+
+ Name der Suchmaschine
+ Zu verwendender Such-String
+ Speichern
+
+
+ Beispiel: example.com/search/?q=%s
+
+ Neue Suchmaschine hinzugefügt.
+
+ Name der Suchmaschine eingeben
+ Es ist bereits eine Suchmaschine installiert, die diesen Namen verwendet.
+
+ Such-String eingeben
+
+ Überprüfen Sie, ob der Such-String dem Beispielformat entspricht
+
+
+ Eingabefeld leeren
+
+
+ Schließen
+
+
+ Browser-Chronik löschen
+
+
+ Offene Tabs: %1$s
+
+
+ Sichere Verbindung
+
+
+ Wird geladen …
+
+
+ Website geladen
+
+
+ Weitere Optionen
+
+
+ Schaltfläche für weitere Optionen
+
+
+ Vorwärts navigieren
+
+
+ Website neu laden
+
+
+ Rückwärts navigieren
+
+
+ Laden der Website stoppen
+
+
+ Zurück zur vorherigen App
+
+
+ Zahl blockierter Verfolger
+
+
+ Verfolger blockieren
+
+ Ihre Rechte
+
+ Link in anderer App öffnen
+
+ Sie können %1$s verlassen, um diesen Link in %2$s zu öffnen.
+
+ App zum Öffnen des Links finden
+
+ Anscheinend kann keine der Apps auf Ihrem Gerät diesen Link öffnen. Sie können %1$s verlassen und bei %2$s nach einer App dafür suchen.
+
+ Privaten Modus verlassen?
+
+
+ %1$s abgeschlossen
+
+
+ Öffnen
+
+
+
+
+
+
+
+
+
+
+ Zu Verknüpfungen hinzugefügt!
+
+ Server nicht gefunden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Schließen
+
+
+
+ Willkommen bei %1$s
+
+
+ Schnell. Privat. Keine Ablenkungen.
+
+
+ Erste Schritte
+
+
+
+ %1$s ist nicht wie andere Browser
+
+
+ Wir löschen Ihre Chronik und verbessern so den Datenschutz, wenn Sie die App schließen.
+
+
+
+ Legen Sie %1$s als Standard fest, um Ihre Daten bei jedem geöffneten Link zu schützen.
+
+
+ Als Standardbrowser festlegen
+
+
+ Überspringen
+
+
+
+ Stärken Sie Ihre Privatsphäre
+
+ Privates Surfen auf einer höheren Stufe. Blockieren Sie Werbung und andere Inhalte, die Sie über Websites hinweg verfolgen können und Seitenladezeiten verlängern.
+
+
+ Ihre Suche, wie Sie möchten
+
+ Suchen Sie etwas anderes? Wählen Sie in den Einstellungen eine andere Standardsuchmaschine.
+
+
+ Fügen Sie Verknüpfungen auf Ihrem Startbildschirm hinzu
+
+ Kehren Sie schnell zu Ihren Lieblings-Websites in %1$s zurück. Wählen Sie einfach im %1$s-Menü „Zum Startbildschirm hinzufügen“.
+
+
+ Machen Sie Privatsphäre zur Gewohnheit
+
+ Verwenden Sie %1$s als Standardbrowser und nutzen Sie die Vorteile des privaten Surfens, wenn Sie aus anderen Apps Webseiten öffnen.
+
+ Ok, verstanden!
+ Überspringen
+ Weiter
+
+
+ –
+
+
+ Hinzufügen
+
+
+ JA
+
+
+ Abbrechen
+
+
+ NEIN
+
+
+ Verknüpfung wird mit deaktiviertem verbesserten Schutz vor Aktivitätenverfolgung geöffnet
+
+
+ Private Surf-Sitzung
+
+
+ Mit Benachrichtigungen können Sie Ihre %1$s-Sitzung mit einem Tippen löschen. Sie müssen weder die App öffnen, noch sehen, was in Ihrem Browser läuft.
+
+
+ Browser-Chronik löschen
+
+
+ Firefox herunterladen
+
+
+
+
+
+
+
+
+ Mozilla Public License und anderen Open-Source-Lizenzen.]]>
+
+
+ hier.]]>
+
+
+ Lizenzen.]]>
+
+
+ GNU General Public License3, die hier verfügbar sind.]]>
+
+
+ Benutzername
+ Passwort
+ Löschen
+
+
+
+ Sichere Verbindung
+ Nicht gesicherte Verbindung
+
+ Verifiziert von: %1$s
+
+
+ Website-Sicherheit
+ Adresse existiert bereits
+
+
+ Seite durchsuchen
+
+
+ Seite durchsuchen
+
+
+ %1$d/%2$d
+
+ %1$d von %2$d
+
+
+ Nächstes Ergebnis suchen
+
+ Vorheriges Ergebnis suchen
+
+ "Seite durchsuchen" deaktivieren
+
+
+
+
+ Desktop-Seite anfordern
+
+
+ Desktop-Website
+
+
+ Adresse kopiert
+
+
+ Entwicklerwerkzeuge
+
+
+ Links in Apps öffnen
+
+
+ Erweitert
+
+
+ Website-Berechtigungen
+
+
+ Reduzierung von Cookie-Bannern
+
+
+ Ein
+
+
+ Aus
+
+
+ Reduzierung von Cookie-Bannern
+
+
+ Sehen Sie weniger Banner, indem Cookie-Anfragen automatisch abgelehnt werden, wann immer möglich.
+
+ -->
+ Reduzierung von Cookie-Bannern
+
+
+ Für diese Website AKTIVIERT
+
+
+ Website derzeit nicht unterstützt
+
+
+ Für diese Website DEAKTIVIERT
+
+
+ Reduzierung von Cookie-Bannern
+
+
+ Für diese Website DEAKTIVIERT
+
+
+ Für diese Website AKTIVIERT
+
+
+ Cookie-Banner-Reduzierung für %1$s aktivieren?
+
+
+ Cookie-Banner-Reduzierung für %1$s deaktivieren?
+
+
+ %1$s löscht die Cookies dieser Website und aktualisiert die Seite. Das Löschen aller Cookies kann Sie abmelden oder Warenkörbe leeren.
+
+
+ %1$s kann versuchen, Cookie-Anfragen automatisch abzulehnen.
+
+
+ Diese Website wird derzeit nicht von der Cookie-Banner-Reduktion unterstützt. Möchten Sie unser Team bitten, diese Website zu überprüfen und in Zukunft Unterstützung hinzuzufügen?
+
+
+ Abbrechen
+
+
+ Unterstützung anfordern
+
+
+ Anfrage an Hilfe-Website übermittelt.
+
+
+ Anfrage an Hilfe-Website übermittelt.
+
+
+
+ %1$s versucht, Cookie-Anfragen abzulehnen, um störende Cookie-Banner zu schließen.\n\nVerwalten Sie die Cookie-Banner-Einstellungen in den %2$s.
+
+
+ Einstellungen
+
+
+ Automatische Wiedergabe
+
+
+ Zum Erlauben:
+
+
+ 1. Öffnen Sie die Android-Einstellungen
+
+
+ Berechtigungen]]>
+
+
+ Einstellungen öffnen
+
+
+ %1$s auf AN]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Ort
+
+
+ Benachrichtigung
+
+
+ Inhalte mit DRM-Kopierschutz
+
+
+ Um Erlaubnis fragen
+
+
+ Blockiert
+
+
+ Erlaubt
+
+
+ Von Android blockiert
+
+
+ Audio und Video erlauben
+
+
+ Nur Audio blockieren
+
+
+ Empfohlen
+
+
+ Audio und Video blockieren
+
+
+ Studien
+
+
+ Firefox darf von Zeit zu Zeit Studien installieren und durchführen.
+
+
+ Weitere Informationen
+
+
+ Die Anwendung wird beendet, um die Änderungen zu übernehmen
+
+
+ Entfernen
+
+
+ Aktiv
+
+
+ Abgeschlossen
+
+
+ Externes Debugging über USB/WLAN
+
+
+ Entsperren
+
+
+ Nutzen Sie Ihren Fingerabdruck zur Bestätigung
+
+
+ Sie können Ihren Fingerabdruck verwenden, um Ihre aktuelle App-Sitzung fortzusetzen.
+
+
+ Link in neuer Sitzung öffnen
+
+
+ Grafik: Fingerabdruck
+
+
+ Fingerabdruck nicht erkannt. Versuchen Sie es erneut.
+
+
+ Finger wurde zu schnell bewegt. Versuchen Sie es erneut.
+
+
+ Suchvorschläge anzeigen?
+
+
+ Um Ihnen Suchvorschläge anzuzeigen, muss %1$s Ihre Eingaben in die Adressleiste an die Suchmaschine senden.
+
+
+ Nein
+
+
+ Ja
+
+
+ Einige Suchmaschinen können keine Vorschläge anzeigen.
+
+
+ Schließen
+
+
+
+
+ Verhält sich die Website ungewohnt?\n Deaktivieren Sie den Schutz vor Aktivitätenverfolgung
+
+
+
+
+
+ Jeden Link in %1$s öffnen\n Stellen Sie %1$s als Standardbrowser ein
+
+
+ Autovervollständigung für meistbesuchte Websites\n Drücken Sie lange auf eine URL in der Adressleiste
+
+
+ Einen Link in einem neuen Tab öffnen\n Drücken Sie lange auf einen Link in einer Seite
+
+
+ Tipps im Startbildschirm abschalten
+
+
+ Neuer Tab geöffnet
+
+
+ Umschalten
+
+
+ Vollbildmodus wird gestartet
+
+
+ Sofort zum Link im neuen Tab wechseln
+
+
+ Potenziell gefährliche und betrügerische Websites blockieren
+
+ Blockieren Sie gemeldete betrügerische Websites und solche, die Sie angreifen, Schadsoftware oder unerwünschte Software verteilen.
+
+
+ Nur-HTTPS-Modus
+
+
+ Automatisch versuchen, eine Verbindung zu Websites herzustellen, die das HTTPS-Verschlüsselungsprotokoll verwenden, um die Sicherheit zu erhöhen.
+
+
+ Ausnahmen
+
+ Das Blockieren von Seitenelementen ist für diese Websites deaktiviert.
+
+ Entfernen
+
+ Alle Websites entfernen
+
+
+ Cookies blockieren
+
+
+ Möchten Sie Cookies blockieren?
+
+
+ Tab ist abgestürzt
+
+ Es tut uns leid. Wir haben ein Problem mit diesem Tab.
+
+ Als privater Browser speichern wir diesen Tab nicht und können ihn nicht wiederherstellen.
+
+ Tab schließen
+
+
+
+
+
+ Absturzbericht an Mozilla senden
+
+
+
+
+ Tracker blockiert seit %s
+
+ Inhalt
+
+ Werbung
+
+ Soziale Netzwerke
+
+ Analytik
+
+ Verbesserter Tracking-Schutz
+
+ Der Schutz für diese Website ist deaktiviert
+
+ Der Schutz für diese Website ist aktiviert
+
+ Verbindung ist sicher
+
+ Verbindung ist nicht sicher
+
+ Zu blockierende Tracker und Skripte
+
+
+ Zurück
+
+
+
+ Entfernen
+
+
+ Umbenennen
+
+ Umbenennen
+
+
+ Name der Verknüpfung
+
+
+ Gespeicherte und geteilte Grafiken <b>werden nicht</b> gelöscht, wenn Sie die Chronik von %1$s löschen.
+
+
+
+ Theme
+
+ Hell
+
+ Dunkel
+
+ Durch Energiespareinstellungen festgelegt
+
+ Geräte-Theme beachten
+
+
+
+ Diese Website unterstützt kein HTTPS
+
+
+ Weitere Informationen
+ Ändern Sie diese Einstellung unter Einstellungen > Datenschutz & Sicherheit > Sicherheit.]]>
+
+
+ Verbindung nicht sicher
+
+
+
+ Wenn Sie in der Vergangenheit erfolgreich mit diesem Server verbunden waren, tritt der Fehler möglicherweise nur temporär auf.
+ ]]>
+
+
+ Jemand könnte versuchen, sich als die Website auszugeben und fortfahren könnte riskant sein.
+
+ %1$s vertraut %2$s nicht, weil der Aussteller des Zertifikats unbekannt ist, das Zertifikat vom Austeller selbst signiert wurde oder der Server nicht die korrekten Zwischen-Zertifikate sendet.
+ ]]>
+
+
+
+ Tab schließen
+
+
+
+ Erwischt! Wir haben diese Seite daran gehindert, Sie auszuspionieren. Tippen Sie jederzeit auf den Schild, um Informationen darüber zu erhalten, was wir blockieren.
+
+
+ Pop-up schließen
+
+
+
+ Sie sind geschützt!
+
+ Diese Standardeinstellungen bieten starken Schutz. Es ist jedoch einfach, die Einstellungen an Ihre spezifischen Anforderungen anzupassen.
+
+ Schließen
+
+
+ Tippen Sie hier, um alles zu löschen – Chronik, Cookies, einfach alles – und in einem neuen Tab neu zu beginnen.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Schließen
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Such-Widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Surf-Chronik gelöscht! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Starten Sie Ihre private Surf-Sitzung, und wir blockieren Tracker und bei der Gelegenheit auch noch andere schädliche Elemente.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Surfen Sie privat weiter, aber starten Sie beim nächsten Mal schneller mit dem %1$s-Widget auf Ihrem Startbildschirm.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Widget zum Startbildschirm hinzufügen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget zum Startbildschirm hinzugefügt
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-dsb/strings.xml b/mobile/android/focus-android/app/src/main/res/values-dsb/strings.xml
new file mode 100644
index 0000000000..ad8f675190
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-dsb/strings.xml
@@ -0,0 +1,1119 @@
+
+
+
+
+
+
+
+
+ Pśetergnuś
+
+ W pórěźe
+
+ Składowaś
+
+
+ Adresu pytaś abo zapódaś
+
+ Awtomatiski priwatny modus.\nPśeglědowaś. Lašowaś. Wóspjetowaś.
+
+
+ Waša pśeglědowańska historija jo se wulašowała.
+ Pśeglědowańska historija jo se wulašowała
+
+
+ Pśeglědowańska historija rejtarka jo se wulašowała.
+
+
+ Pytanje za %1$s
+
+
+ Źěliś…
+
+
+ Sedłowy problem k wěsći daś
+
+
+ W %1$s wócyniś
+
+
+ Wócyniś w…
+
+
+ Startowej wobrazowce pśidaś
+
+
+ Zwězanjam pśidaś
+
+ Ze zwězanjow wótwónoźeś
+
+
+ Nastajenja
+ Wó
+ Pomoc
+ Waše pšawa
+
+
+ Pśeslědowaki blokěrowane
+
+
+ Gaž to znjemóžnjaśo, se někotare sedłowe problemy rozwězuju
+
+
+ Blokěrowanje wopśimjeśa
+
+ Znjemóžniś, aby se někotare sedła pórěźili
+
+
+ Spěchowany wót %1$s
+
+
+ Źěliś pśez
+
+ Pśeglědowańsku historiju lašowaś?
+ Pótusniśo toś tu powěźeńku abo lašujśo ju, aby swóju pśeglědowańsku historiju wěsće wulašował.
+
+
+ Pótusniśo toś tu powěźeńku abo zjěźćo pśez ju, aby swóju pśeglědowańsku historiju wěsće wulašował.
+
+ Pśeglědowańsku historiju lašowaś
+
+
+ Wócyniś
+
+
+ Lašowaś a wócyniś
+
+
+ Lašowaś
+
+
+ Pśeglědowańsku historiju lašowaś
+
+
+
+ Wulašowaś a wócyniś
+
+
+ %1$s lašowaś a wócyniś
+
+
+
+ W Focus pytaś
+
+ W Klar pytaś
+
+ W Focus Beta pytaś
+
+ W Focus Nightly pytaś
+
+
+ %1$s dajo was kontrolu wobchowaś.
+Wužywajśo jen ako priwatny wobglědowak:
+
+ Pytajśo a pśeglědujśo direktnje w nałoženju
+ Blokěrujśo pśeslědowaki (abo aktualizěrujśo nastajenja, aby pśeslědowaki dowólił)
+ Lašujśo cookieje a pytańsku a pśeglědowańsku historiju
+
+
+%1$s se wót Mozilla wuwija. Naša misija jo spěchowanje strowego, wótwórjonego interneta.
+Dalšne informacije
]]>
+
+
+ Priwatnosć a wěstota
+
+
+ Slědowanje, cookieje, datowe wuběrki
+
+
+ Standard nastajiś, awtomatiske wudopołnjenje
+
+
+
+
+ Wó %1$s, pomoc
+
+
+ Pólěpšony slědowański šćit
+
+
+ Webwopśimjeśe
+
+
+ Pśešaltowanje nałoženjow
+
+
+ Powšykne
+
+
+ Standardny wobglědowak, rěc
+
+
+ Zběranje a wužywanje datow
+
+ Pytaś
+
+
+ Pytańske naraźenja wobstaraś
+
+ %1$s se na wašu pytnicu pósćelo, což w adresowem pólu zapódajośo
+
+
+ Standard
+
+
+ Pytnica
+
+
+ Zašaltowany
+
+
+ Wušaltowany
+
+
+ Awtomatiske wudopołnjenje URL
+
+
+ Za nejlubše sedła
+
+
+ Zmóžniś, aby %s wěcej ako 450 woblubowanych adresow w adresowrm pólu wudopołnił.
+
+
+ Za sedła, kótarež pśidawaśo
+
+
+ Zmóžniś, až %s waše nejlubše URL awtomatiski wudopołnjujo.
+
+
+ Sedła zastojaś
+
+
+ Sedła zastojaś
+
+
+ + Swójsku adresu pśidaś
+
+
+ Waša lisćina awtowudopołnjenjow:
+
+
+ URL pśidaś
+
+
+ Swójsku adresu pśidaś
+
+
+ Swójski URL pśidaś
+
+
+ Wótkaz za awtowudopołnjenje pśidaś
+
+
+ Cookieje a sedłowe daty
+
+
+ Datowe wuběrki
+
+
+ Swójske adrese wótwónoźeś
+
+
+ Dalšne informacije
+
+
+ Swójske adrese za wudopołnjenje pśidaś a zastojaś.
+
+
+ Adresa, kótaraž ma se pśidaś
+
+
+ Adresu zasajźiś abo zapódaś
+
+
+ Pśikład: mozilla.org
+
+
+ Pśikład: example.com
+
+
+ Nowa swójska adresa pśidana.
+
+
+ Wótwónoźeś
+
+
+ Wótwónoźeś
+
+
+ Pśeglědajśo zapódanu adresu.
+
+ Rěc
+
+ Systemowy standard
+
+ Priwatnosć
+ Wabjeńske pśeslědowaki blokěrowaś
+ Někotare wabjeńske anonse woglědy sedłow slěduju, samo gaby wy na anonse njekliknuł
+ Analyzowe pśeslědowaki blokěrowaś
+ Z tym se aktiwity ako pótuskowanje a kulanje pśez boki gromaźe, analyzěruju a měrje
+ Socialne pśeslědowaki blokěrowaś
+ Na sedłach zasajźone, aby wóne waše woglědy slědowali a funkcije ako tłocaški za źělenje zwobraznili
+ Druge wopśimjeśowe pśeslědowaki blokěrowaś
+ To móžo wjasć k tomu, až se někotare boki njewšednje zaźarže
+ Cookieje blokěrowaś
+
+
+ Ně, źěkujom se
+ Jano slědujuce cookieje tśeśich póbitowarjow blokěrowaś
+ Jano cookieje tśeśich póbitowarjow blokěrowaś
+
+ Cookieje někotarych sedłow blokěrowaś
+ Jo, pšosym
+
+
+ Wužywajśo palcowy wótśišć, aby nałoženje wótwórił
+
+
+ Wótwóŕśo z pomocu palcowego wóśišća, jolic sćo pśidał zwězanja abo gaž websedło jo južo w %s wócynjone.
+
+
+ Tarnowanje
+
+ Webboki schowaś, gaž se nałoženja pśešaltuju a fota wobrazowki blokěrowaś.
+
+ Wěstota
+
+ Wugbaśe
+ Webpisma blokěrowaś
+
+ Móžo k tomu wjasć, až symbole abo wobraze feluju
+
+ JavaScript blokěrowaś
+
+ Boki se malsnjej zacytaju, mógli se pak teke na njewócakowanu wašnju zaźaržaś
+
+
+ %1$s ako standardny wobglědowak nastajiś
+
+ Mozilla
+ Wužywańske daty pósłaś
+
+
+ Dalšne informacije
+
+
+ Mozilla se procujo, jano informacije gromaźiś, z kótarymiž móžomy %1$s za kuždego póbitowaś a pólěpšyś.
+
+
+ Powěźeńka priwatnosći
+
+
+ Licencne informacije
+
+
+ Biblioteki, kótarež wužywamy
+
+
+ %s | OSS-biblioteki
+
+
+ Wó %1$s
+
+
+ Zainstalěrowane pytnice
+
+
+ Pytnicu wubraś
+
+
+ Standardne pytnice wótnowiś
+
+
+ + Dalšnu pytnicu pśidaś
+ Pytnice wótwónoźeś
+ Wótwónoźeś
+
+
+ Dalšnu pytnicu pśidaś
+
+ Wubjeŕśo swóju nejlubšu pytnicu:
+
+
+ Pytnicu pśidaś
+
+ Mě pytnice
+ Pytański wuraz, kótaryž ma se wužywaś
+ Składowaś
+
+
+ Pśikład: example.com/search/?q=%s
+
+ Nowa pytnica jo se pśidała.
+
+ Mě pytnice zapódaś
+ Jo južo pytnica zainstalěrowana, kótaraž toś to mě wužywa.
+
+ Pytański wuraz zapódaś
+
+ Pśeglědajśo, lěc pytański wuraz pśikładowemu formatoju wótpowědujo
+
+
+ Zapódaśe wuprozniś
+
+
+ Zachyśiś
+
+
+ Pśeglědowańsku historiju lašowaś
+
+
+ Wócynjone rejtarki: %1$s
+
+
+ Wěsty zwisk
+
+
+ Zacytanje
+
+
+ Websedło jo se zacytało
+
+
+ Dalšne nastajenja
+
+
+ Tocašk dalšnych nastajenjow
+
+
+ Doprědka
+
+
+ Websedło znowego zacytaś
+
+
+ Slědk
+
+
+ Cytanje websedła zastajiś
+
+
+ Slědk k pjerwjejšnemu nałoženjeju
+
+
+ Licba zablokěrowanych pśeslědowakow
+
+
+ Pśeslědowaki blokěrowaś
+
+ Waše pšawa
+
+ Wótkaz w drugem nałoženju wócyniś
+
+ Móžośo %1$s spušćiś, aby toś ten wótkaz w %2$s wócynił.
+
+ Nałoženje pytaś, kótarež móžo wótkaz wócyniś
+
+ Žedno z nałoženjow na wašom rěźe njamóžo toś ten wótkaz wócyniś. Móžośo %1$s spušćiś, aby %2$s za nałoženim pśepytował.
+
+ Priwatny modus spušćiś?
+
+
+ %1$s dokóńcony
+
+
+ Wócyniś
+
+
+
+
+
+
+
+
+
+
+ Zwězanjam pśidane!
+
+ Serwer njejo se namakał
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zacyniś
+
+
+
+ Witajśo k %1$s
+
+
+ Malsny. Priwatny. Žedne wótchylenja.
+
+
+ Prědne kšace
+
+
+
+
+ %1$s njejo ako druge wobglědowaki
+
+
+ Lašujomy wašu historiju, gaž nałoženje za pśidatnu priwatnosć zacynjaśo.
+
+
+
+ Cyńśo %1$s k swójomu standardnemu wobglědowakoju, aby swóje daty z kuždym wótkazom, kótaryž wócynjaśo, šćitał.
+
+
+ Ako standardny wobglědowak nastajiś
+
+
+ Pśeskócyś
+
+
+
+ Zmocniśo swóju priwatnosć
+
+ Zwigniśo priwatne pśeglědowanje na pśiducu wušu rowninu. Blokěrujśo wabjenje a hynakše wopśimjeśe, kótarež móžośo pśez sedła slědowaś a zacyitańske case bokow pódlejšyś.
+
+
+ Wašo pytanje ako wy jo cośo
+
+ Pytaśo za něcym drugim? Wubjeŕśo drugu standardnu pytnicu w nastajenjach.
+
+
+ Pśidajśo swójej startowej wobrazowce tastowe skrotconki
+
+ Wrośćo se malsnjej k swójim nejlubšym sedłam w %1$s. Wubjeŕśo jadnorje „Startowej wobrazowce pśidaś“ z menija %1$s.
+
+
+ Pśiwucćo se priwatnosć
+
+ Nastajśo %1$s ako swój standardny wobglědowak a mějśo wužytk z priwatnego pśeglědowanja, gaž webboki z drugich nałoženjow wócynjaśo.
+
+ W pórěźe, som zrozměł!
+ Pśeskócyś
+ Dalej
+
+
+ -
+
+
+ Pśidaś
+
+
+ JO
+
+
+ Pśetergnuś
+
+
+ NĚ
+
+
+ Zwězanje se ze znjemóžnjonym pólěpšonym šlědowańskim šćitom wócynijo
+
+
+ Priwatne pósejźenje
+
+
+ Powěźeńki wam zmóžnjaju, waše pósejźenje %1$s z dotyknjenim wulašowaś. Njetrjebaśo nałoženje wócyniś abo glědaś, což we wašom wobglědowaku běžy.
+
+
+ Pśeglědowańsku historiju wulašowaś
+
+
+ Firefox ześěgnuś
+
+
+
+
+
+
+
+
+ Mozilla Public License a drugimi licencami wótwórjonego žrědła k dispoziciji staja.]]>
+
+
+ how.]]>
+
+
+ licencach wótwórjonego žrědła k dispoziciji.]]>
+
+
+ General Public Licence v3 wužywa, kótarež su how k dispoziciji.]]>
+
+
+ Wužywarske mě
+ Gronidło
+ Wuprozniś
+
+
+
+ Wěsty zwisk
+ Njewěsty zwisk
+
+ Pśeglědany wót: %1$s
+
+
+ Sedłowa wěstota
+ URL južo eksistěrujo
+
+
+ Na boku pytaś
+
+
+ Na boku pytaś
+
+
+ %1$d/%2$d
+
+ %1$d z %2$d
+
+
+ Pśiducy wuslědk namakaś
+
+ Pjerwjejšny wuslědk namakaś
+
+ Na boku pytaś znjemóžniś
+
+
+
+
+ Desktopowe sedło pominaś
+
+
+ Desktopowe sedło
+
+
+ URL jo kopěrowany
+
+
+ Wuwijarske rědy
+
+
+ Wótkaze w nałoženjach wócyniś
+
+
+ Rozšyrjone
+
+
+ Sedłowe pšawa
+
+
+ Reducěrowanje cookiejowych chórgojow
+
+
+ Zašaltowany
+
+
+ Wušaltowany
+
+
+ Reducěrowanje cookiejowych chórgojow
+
+
+ Wótpokažćo cookiejowe napšašowanja, aby mjenjej chórgojow wiźeł, jolic móžno.
+
+ -->
+ Reducěrowanje cookiejowych chórgojow
+
+
+ Za toś to sedło ZMÓŽNJONY
+
+
+ Sedło se tuchylu njepódpěra
+
+
+ Za toś to sedło ZNJEMÓŽNJONY
+
+
+ Reducěrowanje cookiejowych chórgojow
+
+
+ Za toś to sedło ZNJEMÓŽNJONY
+
+
+ Za toś to sedło ZMÓŽNJONY
+
+
+ Reducěrowanje cookiejowych chórgojow za %1$s zmóžniś?
+
+
+ Reducěrowanje cookiejowych chórgojow za %1$s znjemóžniś?
+
+
+ %1$s cookieje sedła lašujo a buźo bok aktualizěrowaś. Lašowanje wšych cookiejow móžo was pśizjawiś abo nakupowańske wózyki wuprozniś.
+
+
+ %1$s móžo wopytaś, cookiejowe napšašowanja awtomatiski wótpokazaś.
+
+
+ Toś to sedło se tuchylu pśez redukciju cookiejowych chórgojow. Cośo naš team pšosyś, toś to websedło pśeglědowaś a pomoc w pśichoźe pśidaś?
+
+
+ Pśetergnuś
+
+
+ Pomoc póžedaś
+
+
+ Napšašowanje na sedło pomocy jo se wótpósłało.
+
+
+ Napšašowanje na sedło pomocy jo se wótpósłało.
+
+
+
+ %1$s wopytujo, cookiejowe napšašowanja wótpokazowaś, aby njepśigódne cookiejowe chórgoji zachyśił.\n\nZastojśo nastajenja cookiejowych chórgojow w %2$s.
+
+ nastajenja
+
+
+ Awtomatiske wótgraśe
+
+
+ Aby to dowólił:
+
+
+ 1. Źiśo k nastajenjam Android
+
+
+ Berechtigungen (Pšawa)]]>
+
+
+ K nastajenjam
+
+
+ %1$s]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Městno
+
+
+ Powěźeńka
+
+
+ Wopśimjeśe wóźone pśez DRM
+
+
+ Wó dowólnosć se pšašaś
+
+
+ Blokěrowany
+
+
+ Dowólony
+
+
+ Pśez Android zablokěrowany
+
+
+ Awdio a wideo dowóliś
+
+
+ Jano awdio blokěrowaś
+
+
+ Dopórucone
+
+
+ Awdio a wideo blokěrowaś
+
+
+ Studije
+
+
+ Firefox móžo wótergi studije instalěrowaś a pśewjasć.
+
+
+ Dalšne informacije
+
+
+ Nałoženje se skóńcyjo, aby se změny wustatkowali
+
+
+ Wótwónoźeś
+
+
+ Aktiwny
+
+
+ Dokóńcone
+
+
+ Daloke pytanje zmólkow pśez USB/WLAN
+
+
+ Wótwóriś
+
+
+ Wobkšuśćo z pomocu swójogo palcowego wótśišća
+
+
+ Móžośo swój palcowy wótśišć, aby z aktualnem pósejźenim swójogo nałoženja pókšacował.
+
+
+ Wótkaz w nowem pósejźenju wócyniś
+
+
+ Symbol palcowego wótśišća
+
+
+ Palcowy wótśišć njejo se spóznał. Wopytajśo hyšći raz.
+
+
+ Palc jo se pśemalsnje gibnuł. Wopytajśo hyšći raz.
+
+
+ Pytańske naraźenja pokazaś?
+
+
+ Aby naraźenja dostał, musy %1$s wopśimjeśe, kótaryž sćo zapódał do adresowego póla, na pytnicu słaś.
+
+
+ Ně
+
+
+ Jo
+
+
+ Někotare pytnice njamógu naraźenja pokazaś.
+
+
+ Zachyśiś
+
+
+
+
+ Sedło se na njewócakowanu wašnju zaźaržy?\nWopytajśo slědowański šćit znjemóžniś
+
+
+ Startowej wobrazowce pśidaś]]>
+
+
+ Kuždy wótkaz w %1$s wócyniś\n%1$s ako standardny wobglědowak nastajiś
+
+
+ URL za sedła, ku kótarymž se nejcesćej woglědujośo, awtomatiski wudopołniś\nTłocćo dłujko na URL w adresowem pólu
+
+
+ Wótkaz w nowem rejtarku wócyniś\nTłocćo dłujko na wótkaz na boku
+
+
+ Tipy na startowej wobrazowce znjemóžniś
+
+
+ Nowy rejtark jo se wócynił
+
+
+ Pśešaltowaś
+
+
+ Połna wobrazowka se pokazujo
+
+
+ Ned k wótkazoju w nowem rejtarku pśejś
+
+
+ Ptencielnje tšašne a wobšudnikojske sedła blokěrowaś
+
+ Blokěrujśo wuzjawjone wobšudnikojske a napadowe sedła, škódne sedła a sedła z njewitaneju softwaru.
+
+
+ Modus Jano-HTTPS
+
+
+ Wopytujo z pomocu koděrowańskego protokola HTTPS za pówušonu wěstotu awtomatiski ze sedłami zwězaś.
+
+
+ Wuwześa
+
+ Sćo znjemóžnił blokěrowanje wopśimjeśa za toś te sedła.
+
+ Wótwónoźeś
+
+ Wšykne sedła wótwónoźeś
+
+
+ Cookieje blokěrowaś
+
+
+ Cośo cookieje blokěrowaś?
+
+
+ Rejtark jo wowalił
+
+ Bóžko mamy problem z toś tym rejtarkom.
+
+ Ako priwatny wobglědowak njeskładujomy nigda a njamóžomy toś ten rejtark wótnowiś.
+
+ Rejtark zacyniś
+
+
+
+
+
+ Mozilla rozpšawu wowalenja pósłaś
+
+
+
+
+ Pśeslědowaki su zablokěrowane wót %s
+
+ Wopśimjeśe
+
+ Wabjenje
+
+ Socialne seśi
+
+ Analytika
+
+ Pólěpšony slědowański šćit
+
+ Šćit jo znjemóžnjony za toś to sedło
+
+ Šćit jo zmóžnjony za toś to sedło
+
+ Zwisk jo wěsty
+
+ Zwisk njejo wěsty
+
+ Pśeslědowaki a skripty, kótarež se maju blokěrowaś
+
+
+ Slědk hyś
+
+
+
+ Wótwónoźeś
+
+
+ Pśemjeniś
+
+ Pśemjeniś
+
+
+ Mě zwězanja
+
+
+ Skłaźone a źělone wobraze <b>se nje</b>wulašuju, gaž historiju %1$s lašujośo.
+
+
+
+ Drastwa
+
+ Swětły
+
+ Śamny
+
+ Pó zažarjeńskich nastajenjach baterije
+
+ Na rědowu drastwu źiwaś
+
+
+
+ Toś to sedło HTTPS njepódpěra
+
+
+ Dalšne informacije
+ Změńśo toś to nastajenje w Nastajenja > Priwatnosć a wěstota > Wěstota.]]>
+
+
+ Zwisk njejo wěsty
+
+
+
+
+ Jolic sćo był w zajźonosći ze serwerom wuspěšnje zwězany, móžo zmólka nachylna byś.
+ ]]>
+
+
+ Něchten mógał wopytaś, sedło za swójo wudaś, a pókšacowanje mógło riskantne byś.
+
+ %1$s %2$s njedowěri, dokulaž jogo certifikatowy wudawaŕ jo njeznaty, certifkat jo sebjesigněrowany abo serwer korektne mjazycertifikaty njesćelo.
+ ]]>
+
+
+
+ Rejtarik zacyniś
+
+
+
+ Załapjone! Smy zajźowali sedłoju was wusnuchliś. Pótusniśo kuždy cas šćita, aby wiźeł, což my blokěrujomy.
+
+
+ Wuskokujuce wokno zacyniś
+
+
+
+ Sćo šćitany!
+
+ Toś te standardne nastajenja mócny šćit póbituju. Ale jo lažko, nastajenja wašym specifiskim pótrjebam pśiměriś.
+
+ Zachyśiś
+
+
+ Pótusniśo how, aby wšykno do papjernika pśesunuł – historiju, cookieje, wšykno – a startujśo znowego na nowem rejtariku.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Zacyniś
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Pytański asistent
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Pśeglědowańska historija jo se wulašowała! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Zachopśo swóje priwatne pśeglědowańske pósejźenje a my buźomy pśeslědowaki a druge škódne elementy pśi toś tej góźbje blokěrowaś.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Pśewóstajijomy wašomu priwatnemu modusoju, ale startujśo pśiducy raz malsnjej z asistentom %1$s na swójej startowej wobrazowce.
+
+
+ Asistent startowej wobrazowce pśidaś
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Asistent jo se pśidał startowej wobrazowce
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-el/strings.xml b/mobile/android/focus-android/app/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000000..d9167ba04c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-el/strings.xml
@@ -0,0 +1,1128 @@
+
+
+
+
+
+
+
+
+ Ακύρωση
+
+ OK
+
+ Αποθήκευση
+
+
+ Αναζήτηση ή εισαγωγή διεύθυνσης
+
+ Αυτόματη ιδιωτική περιήγηση.\nΠεριήγηση. Διαγραφή. Επανάληψη.
+
+
+ Το ιστορικό περιήγησης έχει διαγραφτεί.
+
+ Το ιστορικό περιήγησης απαλείφθηκε
+
+
+ Το ιστορικό της καρτέλας έχει διαγραφτεί.
+
+
+ Αναζήτηση για «%1$s»
+
+
+ Κοινή χρήση…
+
+
+ Αναφορά ζητήματος ιστοτόπου
+
+
+ Άνοιγμα σε %1$s
+
+
+ Άνοιγμα σε…
+
+
+ Προσθήκη στην αρχική οθόνη
+
+
+ Προσθήκη συντόμευσης
+
+ Αφαίρεση συντόμευσης
+
+
+ Ρυθμίσεις
+ Πληροφορίες
+ Βοήθεια
+ Τα δικαιώματά σας
+
+
+ Αποκλεισμένοι ιχνηλάτες
+
+
+ Η απενεργοποίηση αυτού ίσως διορθώσει μερικά προβλήματα
+
+
+ Φραγή περιεχομένου
+
+
+ Απενεργοποιήστε το για διόρθωση μερικών ιστοτόπων
+
+
+ Με την υποστήριξη του %1$s
+
+
+ Κοινοποίηση μέσω
+
+ Διαγραφή ιστορικού περιήγησης;
+ Πατήστε ή εκκαθαρίστε αυτήν την ειδοποίηση για να διαγράψετε με ασφάλεια το ιστορικό περιήγησής σας.
+
+
+ Πατήστε ή σύρετε αυτήν την ειδοποίηση για να διαγράψετε με ασφάλεια το ιστορικό περιήγησής σας.
+
+ Διαγραφή ιστορικού περιήγησης
+
+
+ Άνοιγμα
+
+
+ Διαγραφή και άνοιγμα
+
+
+ Διαγραφή
+
+
+ Διαγραφή ιστορικού περιήγησης
+
+
+
+ Διαγραφή και άνοιγμα
+
+
+ Διαγραφή και άνοιγμα %1$s
+
+
+
+ Αναζήτηση στο Focus
+
+ Αναζήτηση στο Klar
+
+ Αναζήτηση στο Focus Beta
+
+ Αναζήτηση στο Focus Nightly
+
+
+ Το %1$s σάς δίνει τον έλεγχο.
+Χρησιμοποιήστε το για:
+
+ Αναζήτηση και περιήγηση
+ Φραγή ιχνηλατών (ή αποδοχή, ενημερώνοντας τις ρυθμίσεις)
+ Διαγραφή των cookie και του ιστορικού αναζητήσεων και περιήγησης
+
+
+Το %1$s αναπτύσσεται από τη Mozilla. Αποστολή μας είναι ένα υγιές και ανοικτό διαδίκτυο.
+Μάθετε περισσότερα
]]>
+
+
+ Απόρρητο και ασφάλεια
+
+
+ Επιλογές καταγραφής, cookie, δεδομένων
+
+
+ Ορισμός ως προεπιλογή, αυτόματη συμπλήρωση
+
+
+
+
+ Σχετικά με το %1$s, βοήθεια
+
+
+ Ενισχυμένη προστασία από καταγραφή
+
+
+ Περιεχόμενο ιστού
+
+
+ Εναλλαγή εφαρμογών
+
+
+ Γενικά
+
+
+ Προεπιλεγμένος φυλλομετρητής, γλώσσα
+
+
+ Συλλογή και χρήση δεδομένων
+
+ Αναζήτηση
+
+
+ Λήψη προτάσεων αναζήτησης
+
+ Το %1$s θα στέλνει όσα πληκτρολογείτε στη γραμμή διευθύνσεων στη μηχανή αναζήτησής σας
+
+
+ Προεπιλογή
+
+
+ Μηχανή αναζήτησης
+
+
+ Ενεργό
+
+
+ Ανενεργό
+
+
+ Αυτόματη συμπλήρωση URL
+
+
+ Για κορυφαίους ιστοτόπους
+
+
+ Ενεργοποιήστε το για συμπλήρωση πάνω από 450 δημοφιλών URL στη γραμμή διευθύνσεων του %s.
+
+
+ Για ιστοτόπους που προσθέτετε
+
+
+ Ενεργοποιήστε το για συμπλήρωση των αγαπημένων σας URL στο %s.
+
+
+ Διαχείριση ιστοτόπων
+
+
+ Διαχείριση ιστοτόπων
+
+
+ + Προσθήκη προσαρμοσμένου URL
+
+
+ Λίστα αυτόματης συμπλήρωσης:
+
+
+ Προσθήκη URL
+
+
+ Προσθήκη προσαρμοσμένου URL
+
+
+ Προσθήκη προσαρμοσμένου URL
+
+
+ Προσθήκη συνδέσμου για αυτόματη συμπλήρωση
+
+
+ Cookie και δεδομένα ιστοτόπων
+
+
+ Επιλογές δεδομένων
+
+
+ Αφαίρεση προσαρμοσμένων URL
+
+
+ Μάθετε περισσότερα
+
+
+ Προσθήκη και διαχείριση προσαρμοσμένων URL αυτόματης συμπλήρωσης.
+
+
+ URL για προσθήκη
+
+
+ Επικόλληση ή εισαγωγή URL
+
+
+ Παράδειγμα: mozilla.org
+
+
+ Παράδειγμα: example.com
+
+
+ Προστέθηκε νέο προσαρμοσμένο URL.
+
+
+ Αφαίρεση
+
+
+ Αφαίρεση
+
+
+ Ελέγξτε ξανά το URL που πληκτρολογήσατε.
+
+ Γλώσσα
+
+ Προεπιλογή συστήματος
+
+ Απόρρητο
+ Φραγή ιχνηλατών διαφημίσεων
+ Ορισμένες διαφημίσεις καταγράφουν επισκέψεις σε ιστοτόπους, ακόμη κι αν δεν τις πατήσετε
+ Φραγή ιχνηλατών ανάλυσης
+ Χρησιμοποιούνται για τη συλλογή, την ανάλυση και τη μέτρηση δραστηριοτήτων, όπως πατήματα και κυλίσεις
+ Φραγή ιχνηλατών κοινωνικών δικτύων
+ Ενσωματώνονται σε ιστοτόπους για την καταγραφή των επισκέψεών σας και την εμφάνιση λειτουργιών, όπως κουμπιά κοινοποίησης
+ Φραγή ιχνηλατών άλλου περιεχομένου
+ Η ενεργοποίηση ενδέχεται να προκαλέσει την απρόσμενη συμπεριφορά ορισμένων σελίδων
+ Φραγή cookie
+
+
+ Όχι, ευχαριστώ
+ Φραγή μόνο τρίτων cookie καταγραφής
+ Φραγή μόνο τρίτων cookie
+
+ Φραγή cookie μεταξύ ιστοτόπων
+ Ναι, παρακαλώ
+
+
+ Χρήση αποτυπώματος για ξεκλείδωμα εφαρμογής
+
+
+ Ξεκλείδωμα με αποτύπωμα εάν έχετε προσθέσει συντομεύσεις ή όταν ένας ιστότοπος είναι ήδη ανοικτός στο %s.
+
+
+ Αορατότητα
+
+ Απόκρυψη ιστοσελίδων κατά την εναλλαγή εφαρμογών και φραγή στιγμιότυπων.
+
+ Ασφάλεια
+
+ Επιδόσεις
+ Φραγή γραμματοσειρών ιστού
+
+ Ενδέχεται να οδηγήσει σε απώλεια εικονιδίων ή εικόνων
+
+ Φραγή JavaScript
+
+ Οι σελίδες ενδέχεται να φορτώνονται ταχύτερα, αλλά με απρόσμενες συμπεριφορές
+
+
+ Ορισμός του %1$s ως προεπιλογής
+
+ Mozilla
+ Αποστολή δεδομένων χρήσης
+
+
+ Μάθετε περισσότερα
+
+
+ Η Mozilla προσπαθεί συλλέγει μόνο όσα χρειάζεται για να παρέχει και να βελτιώνει το %1$s για όλους.
+
+
+ Σημείωση απορρήτου
+
+
+ Πληροφορίες άδειας
+
+
+ Βιβλιοθήκες που χρησιμοποιούμε
+
+
+ %s | Βιβλιοθήκες OSS
+
+
+ Σχετικά με το %1$s
+
+
+ Εγκατεστημένες μηχανές αναζήτησης
+
+
+ Επιλογή μηχανής αναζήτησης
+
+
+ Επαναφορά αρχικών μηχανών αναζήτησης
+
+
+ + Προσθήκη άλλης μηχανής αναζήτησης
+ Αφαίρεση μηχανών αναζήτησης
+ Αφαίρεση
+
+
+ Προσθήκη άλλης μηχανής αναζήτησης
+
+ Επιλέξτε την προτιμώμενη μηχανή σας:
+
+
+ Προσθήκη μηχανής αναζήτησης
+
+ Όνομα μηχανής αναζήτησης
+ Όρος αναζήτησης για χρήση
+ Αποθήκευση
+
+
+ Παράδειγμα: example.com/search/?q=%s
+
+ Προστέθηκε νέα μηχανή αναζήτησης.
+
+ Εισαγάγετε το όνομα μηχανής αναζήτησης
+ Μια εγκατεστημένη μηχανή αναζήτησης χρησιμοποιεί ήδη αυτό το όνομα.
+
+ Εισαγάγετε τον όρο αναζήτησης
+
+ Ελέγξτε αν ο όρος αναζήτησης ταιριάζει με τη μορφή του παραδείγματος
+
+
+ Απαλοιφή εισόδου
+
+
+ Απόρριψη
+
+
+ Διαγραφή ιστορικού περιήγησης
+
+
+ Ανοικτές καρτέλες: %1$s
+
+
+ Ασφαλής σύνδεση
+
+
+ Φόρτωση
+
+
+ Ο ιστότοπος φορτώθηκε
+
+
+ Περισσότερες επιλογές
+
+
+ Κουμπί περισσότερων επιλογών
+
+
+ Πλοήγηση προς τα εμπρός
+
+
+ Ανανέωση ιστοτόπου
+
+
+ Πλοήγηση προς τα πίσω
+
+
+ Διακοπή φόρτωσης ιστοτόπου
+
+
+ Επιστροφή στην προηγούμενη εφαρμογή
+
+
+ Αριθμός αποκλεισμένων ιχνηλατών
+
+
+ Φραγή ιχνηλατών
+
+ Τα δικαιώματά σας
+
+ Άνοιγμα συνδέσμου σε άλλη εφαρμογή
+
+ Μπορείτε να φύγετε από το %1$s για να ανοίξετε τον σύνδεσμο στο %2$s.
+
+ Βρείτε μια εφαρμογή που μπορεί να ανοίξει τον σύνδεσμο
+
+ Καμία από τις εφαρμογές της συσκευής σας δεν μπορεί να ανοίξει αυτόν τον σύνδεσμο. Μπορείτε να φύγετε από το %1$s για να αναζητήσετε στο %2$s μια εφαρμογή που να μπορεί να εκτελέσει αυτήν την εργασία.
+
+ Έξοδος από την ιδιωτική περιήγηση;
+
+
+ Το %1$s ολοκληρώθηκε
+
+
+ Άνοιγμα
+
+
+
+
+
+
+
+
+
+
+ Προστέθηκε στις συντομεύσεις!
+
+ Ο διακομιστής δεν βρέθηκε
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Κλείσιμο
+
+
+
+ Καλώς ορίσατε στο %1$s
+
+
+ Γρήγορο. Ιδιωτικό. Χωρίς περισπασμούς.
+
+
+ Έναρξη
+
+
+
+ Το %1$s δεν είναι σαν τα άλλα προγράμματα περιήγησης
+
+
+ Διαγράφουμε το ιστορικό σας όταν κλείνετε την εφαρμογή για επιπλέον ιδιωτικότητα.
+
+
+
+ Ορίστε το %1$s ως προεπιλογή για να προστατεύσετε τα δεδομένα σας με κάθε σύνδεσμο που ανοίγετε.
+
+
+ Ορισμός ως προεπιλογή
+
+
+ Παράλειψη
+
+
+
+ Ενισχύστε το απόρρητό σας
+
+ Μεταβείτε στο επόμενο επίπεδο ιδιωτικής περιήγησης. Αποκλείστε διαφημίσεις και περιεχόμενο που μπορεί να καταγράψει τη δραστηριότητά σας και να επιβραδύνει τη φόρτωση σελίδων.
+
+
+ Αναζήτηση με τον δικό σας τρόπο
+
+ Ψάχνετε κάτι διαφορετικό; Επιλέξτε μια άλλη προεπιλεγμένη μηχανή αναζήτησης στις ρυθμίσεις.
+
+
+ Προσθήκη συντομεύσεων στην αρχική οθόνη
+
+ Επιστρέψτε γρήγορα στους αγαπημένους σας ιστοτόπους στο %1$s. Απλά επιλέξτε «Προσθήκη στην αρχική οθόνη» από το μενού του %1$s.
+
+
+ Κάντε το απόρρητο συνήθεια
+
+ Ορίστε το %1$s ως τον προεπιλεγμένο φυλλομετρητή για ιδιωτική περιήγηση σε ιστοτόπους που ανοίγουν από άλλες εφαρμογές.
+
+ OK, κατάλαβα!
+ Παράλειψη
+ Επόμενο
+
+
+ -
+
+
+ Προσθήκη
+
+
+ ΝΑΙ
+
+
+ Ακύρωση
+
+
+ ΟΧΙ
+
+
+ Η συντόμευση θα ανοίξει χωρίς Ενισχυμένη προστασία από καταγραφή
+
+
+ Συνεδρία ιδιωτικής περιήγησης
+
+
+ Οι ειδοποιήσεις σάς επιτρέπουν να διαγράψετε τη συνεδρία σας στο %1$s με ένα πάτημα. Δεν χρειάζεται να ανοίξετε την εφαρμογή ή να δείτε τι συμβαίνει στον φυλλομετρητή σας.
+
+
+ Διαγραφή ιστορικού περιήγησης
+
+
+ Λήψη του Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License και άλλων αδειών ανοικτού κώδικα.]]>
+
+
+ εδώ.]]>
+
+
+ αδειών ανοικτού κώδικα.]]>
+
+
+ GNU General Public License v3, που βρίσκονται εδώ .]]>
+
+
+ Όνομα χρήστη
+ Κωδικός πρόσβασης
+ Εκκαθάριση
+
+
+
+ Ασφαλής σύνδεση
+ Μη ασφαλής σύνδεση
+
+ Επαληθεύτηκε από: %1$s
+
+
+ Ασφάλεια ιστοτόπου
+ Το URL υπάρχει ήδη
+
+
+ Εύρεση στη σελίδα
+
+
+ Εύρεση στη σελίδα
+
+
+ %1$d/%2$d
+
+ %1$d από %2$d
+
+
+ Εύρεση επόμενου αποτελέσματος
+
+ Εύρεση προηγούμενου αποτελέσματος
+
+ Απόρριψη εύρεσης στη σελίδα
+
+
+
+
+ Ιστότοπος υπολογιστή
+
+
+ Έκδοση υπολογιστή
+
+
+ Το URL αντιγράφτηκε
+
+
+ Εργαλεία προγραμματιστή
+
+
+ Άνοιγμα συνδέσμων σε εφαρμογές
+
+
+ Σύνθετα
+
+
+ Άδειες ιστοτόπων
+
+
+ Μείωση μηνυμάτων για cookie
+
+
+ Ενεργή
+
+
+ Ανενεργή
+
+
+ Μείωση μηνυμάτων για cookie
+
+
+ Δείτε λιγότερα μηνύματα με αυτόματη απόρριψη αιτημάτων για cookie, όταν αυτό είναι δυνατό.
+
+ -->
+ Μείωση μηνυμάτων για cookie
+
+
+ ΕΝΕΡΓΗ για αυτόν τον ιστότοπο
+
+
+ Ο ιστότοπος δεν υποστηρίζεται
+
+
+ ΑΝΕΝΕΡΓΗ για αυτόν τον ιστότοπο
+
+
+ Μείωση μηνυμάτων για cookie
+
+
+ ΑΝΕΝΕΡΓΗ για αυτόν τον ιστότοπο
+
+
+ ΕΝΕΡΓΗ για αυτόν τον ιστότοπο
+
+
+ Ενεργοποίηση μείωσης μηνυμάτων για cookie στο %1$s;
+
+
+ Απενεργοποίηση μείωσης μηνυμάτων για cookie στο %1$s;
+
+
+ Το %1$s θα απαλείψει τα cookie του ιστοτόπου και θα ανανεώσει τη σελίδα. Η απαλοιφή όλων των cookie ενδέχεται να σας αποσυνδέσει ή να αδειάσει τα καλάθια αγορών.
+
+
+ Το %1$s προσπαθεί να απορρίπτει αυτόματα τα αιτήματα για cookie.
+
+
+ Αυτός ο ιστότοπος δεν υποστηρίζεται προς το παρόν από τη Μείωση μηνυμάτων για cookie. Θέλετε να ζητήσετε από την ομάδα μας να ελέγξει αυτόν τον ιστότοπο και να προσθέσει υποστήριξη στο μέλλον;
+
+
+ Ακύρωση
+
+
+ Αίτημα υποστήριξης
+
+
+ Υποβλήθηκε αίτημα για υποστήριξη ιστοτόπου.
+
+
+ Υποβλήθηκε αίτημα για υποστήριξη ιστοτόπου.
+
+
+
+ Το %1$s προσπαθεί να απορρίψει τα αιτήματα για cookie, ώστε να κλείσει τα ενοχλητικά μηνύματα για cookie.\n\nΔιαχειριστείτε τις προτιμήσεις των μηνυμάτων για cookie στις %2$s.
+
+
+ ρυθμίσεις
+
+
+ Αυτόματη αναπαραγωγή
+
+
+ Για να το επιτρέψετε:
+
+
+ 1. Μεταβείτε στις ρυθμίσεις Android
+
+
+ Δικαιώματα]]>
+
+
+ Μετάβαση στις ρυθμίσεις
+
+
+ %1$s]]>
+
+
+ Κάμερα
+
+
+ Μικρόφωνο
+
+
+ Τοποθεσία
+
+
+ Ειδοποιήσεις
+
+
+ Περιεχόμενο με έλεγχο DRM
+
+
+ Ερώτηση για αποδοχή
+
+
+ Φραγή
+
+
+ Αποδοχή
+
+
+ Αποκλείστηκε από το Android
+
+
+ Αποδοχή ήχου και βίντεο
+
+
+ Φραγή ήχου μόνο
+
+
+ Προτείνεται
+
+
+ Φραγή ήχου και βίντεο
+
+
+ Μελέτες
+
+
+ Το Firefox ενδέχεται να εγκαθιστά και να εκτελεί περιστασιακά μελέτες.
+
+
+ Μάθετε περισσότερα
+
+
+ Η εφαρμογή θα κλείσει για την εφαρμογή των αλλαγών
+
+
+ Αφαίρεση
+
+
+ Ενεργές
+
+
+ Ολοκληρωμένες
+
+
+ Απομακρυσμένος έλεγχος σφαλμάτων μέσω USB/Wi-Fi
+
+
+ Ξεκλείδωμα
+
+
+ Επιβεβαίωση με δακτυλικό αποτύπωμα
+
+
+ Μπορείτε να χρησιμοποιήσετε το αποτύπωμά σας για να συνεχίσετε την τρέχουσα συνεδρία της εφαρμογής σας.
+
+
+ Άνοιγμα συνδέσμου σε νέα συνεδρία
+
+
+ Εικονίδιο αποτυπώματος
+
+
+ Το αποτύπωμα δεν αναγνωρίστηκε. Δοκιμάστε ξανά.
+
+
+ Το δάκτυλο κουνήθηκε πολύ γρήγορα. Δοκιμάστε ξανά.
+
+
+ Εμφάνιση προτάσεων αναζήτησης;
+
+
+ Για να λάβετε προτάσεις, το %1$s πρέπει να στείλει ό,τι πληκτρολογείτε στη γραμμή διευθύνσεων στη μηχανή αναζήτησης.
+
+
+ Όχι
+
+
+ Ναι
+
+
+ Μερικές μηχανές αναζήτησης δεν μπορούν να εμφανίσουν προτάσεις.
+
+
+ Απόρριψη
+
+
+
+
+ Απρόσμενη συμπεριφορά σε ιστότοπο;\n
+ Απενεργοποιήστε την προστασία από καταγραφή
+
+
+ Προσθήκη στην αρχική οθόνη]]>
+
+
+ Άνοιγμα κάθε συνδέσμου στο %1$s\n
+ Ορίστε το %1$s ως προεπιλογή
+
+
+
+ Αυτόματη συμπλήρωση URL για συχνούς ιστοτόπους\n
+ Πατήστε παρατεταμένα οποιοδήποτε URL στη γραμμή διευθύνσεων
+
+
+
+ Άνοιγμα συνδέσμου σε νέα καρτέλα\n
+ Πατήστε παρατεταμένο σε οποιονδήποτε σύνδεσμο μιας σελίδας
+
+
+
+ Απενεργοποίηση συμβουλών αρχικής οθόνης
+
+
+ Άνοιξε νέα καρτέλα
+
+
+ Εναλλαγή
+
+
+ Είσοδος σε λειτουργία πλήρους οθόνης
+
+
+ Άμεση αλλαγή στον σύνδεσμο νέας καρτέλας
+
+
+ Φραγή δυνητικά επικίνδυνων και παραπλανητικών ιστοτόπων
+
+
+ Φραγή ιστοτόπων εξαπάτησης, επιθέσεων, κακόβουλου και ανεπιθύμητου λογισμικού.
+
+
+ Λειτουργία «Μόνο HTTPS»
+
+
+ Προσπαθεί αυτόματα να συνδεθεί σε ιστοτόπους με το πρωτόκολλο κρυπτογράφησης HTTPS για αυξημένη ασφάλεια.
+
+
+ Εξαιρέσεις
+
+ Έχετε απενεργοποιήσει τη φραγή περιεχομένου στους εξής ιστοτόπους.
+
+ Αφαίρεση
+
+ Αφαίρεση όλων των ιστοτόπων
+
+
+ Φραγή cookie
+
+
+ Θέλετε να αποκλείσετε τα cookie;
+
+
+ Η καρτέλα κατέρρευσε
+
+ Δυστυχώς, αντιμετωπίζουμε πρόβλημα με αυτήν την καρτέλα.
+
+ Για λόγους ασφαλείας, δεν αποθηκεύουμε ούτε μπορούμε να ανακτήσουμε αυτήν την καρτέλα.
+
+ Κλείσιμο καρτέλας
+
+
+ Αποστολή αναφοράς κατάρρευσης στη Mozilla
+
+
+
+
+ Αποκλεισμένοι ιχνηλάτες από τις %s
+
+ Περιεχόμενο
+
+ Διαφημίσεις
+
+ Κοινωνικά δίκτυα
+
+ Αναλύσεις
+
+ Ενισχυμένη προστασία από καταγραφή
+
+ Η προστασία είναι ΑΝΕΝΕΡΓΗ για τον ιστότοπο
+
+ Η προστασία είναι ΕΝΕΡΓΗ για τον ιστότοπο
+
+ Η σύνδεση είναι ασφαλής
+
+ Η σύνδεση δεν είναι ασφαλής
+
+
+ Ιχνηλάτες και σενάρια για φραγή
+
+
+ Επιστροφή
+
+
+
+ Αφαίρεση
+
+
+ Μετονομασία
+
+ Μετονομασία
+
+
+ Όνομα συντόμευσης
+
+
+ <b>Δεν</b> διαγράφονται αποθηκευμένες και κοινόχρηστες εικόνες κατά την απαλοιφή ιστορικού του %1$s
+
+
+
+ Θέμα
+
+ Ανοιχτόχρωμο
+
+ Σκουρόχρωμο
+
+ Ορισμός από εξοικονόμηση μπαταρίας
+
+ Χρήση θέματος συσκευής
+
+
+
+ Αυτός ο ιστότοπος δεν υποστηρίζει HTTPS
+
+
+ Μάθετε περισσότερα
+ Αλλάξτε αυτήν τη ρύθμιση στις Ρυθμίσεις > Απόρρητο και ασφάλεια > Ασφάλεια.]]>
+
+
+ Μη ασφαλής σύνδεση
+
+
+
+ Εάν έχετε συνδεθεί επιτυχώς σε αυτόν τον διακομιστή στο παρελθόν, το σφάλμα είναι μάλλον προσωρινό.
+ ]]>
+
+
+ Κάποιος ίσως προσπαθεί να μιμηθεί τον ιστότοπο και θα ήταν επικίνδυνο να συνεχίσετε.
+
+ Το %1$s δεν εμπιστεύεται το %2$s , επειδή ο εκδότης του πιστοποιητικού του είναι άγνωστος, το πιστοποιητικό είναι αυτοϋπογεγραμμένο ή ο διακομιστής δεν στέλνει τα σωστά ενδιάμεσα πιστοποιητικά.
+ ]]>
+
+
+
+ Κλείσιμο καρτέλας
+
+
+
+ Εμποδίσαμε την παρακολούθησή σας από αυτόν τον ιστότοπο. Πατήστε την ασπίδα ανά πάσα στιγμή για να δείτε τι αποκλείουμε.
+
+
+ Κλείσιμο παραθύρου
+
+
+
+ Προστατεύεστε!
+
+ Αυτές οι προεπιλεγμένες ρυθμίσεις προσφέρουν ισχυρή προστασία. Αλλά μπορείτε εύκολα να τις προσαρμόσετε για να ταιριάξουν με τις δικές σας ανάγκες.
+
+ Απόρριψη
+
+
+ Πατήστε εδώ για να τα διαγράψετε όλα — ιστορικό, cookie, τα πάντα — και ξεκινήστε από την αρχή σε μια νέα καρτέλα.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Κλείσιμο
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Γραφικό στοιχείο αναζήτησης
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Το ιστορικό περιήγησης διαγράφηκε! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Ξεκινήστε τη συνεδρία ιδιωτικής περιήγησής σας και θα αποκλείσουμε τους ιχνηλάτες και άλλα επιβλαβή στοιχεία.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Θα σας αφήσουμε στην ιδιωτική σας περιήγηση, αλλά ξεκινήστε πιο γρήγορα την επόμενη φορά με το γραφικό στοιχείο %1$s στην αρχική σας οθόνη.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Προσθήκη γραφικού στοιχείου στην αρχική οθόνη
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Το γραφικό στοιχείο προστέθηκε στην αρχική οθόνη
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-en-rCA/strings.xml b/mobile/android/focus-android/app/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000000..99594114ac
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,1118 @@
+
+
+
+
+
+
+
+
+ Cancel
+
+ OK
+
+ Save
+
+
+ Search or enter address
+
+ Automatic private browsing.\nBrowse. Erase. Repeat.
+
+
+ Your browsing history has been erased.
+ Browsing history cleared
+
+
+ Tab’s browsing history has been erased.
+
+
+ Search for %1$s
+
+
+ Share…
+
+
+ Report Site Issue
+
+
+ Open in %1$s
+
+
+ Open in…
+
+
+ Add to Home screen
+
+
+ Add to Shortcuts
+
+ Remove from Shortcuts
+
+
+ Settings
+ About
+ Help
+ Your Rights
+
+
+ Trackers blocked
+
+
+ Turning this off may fix some site problems
+
+
+ Content Blocking
+
+ Turn off to fix some sites
+
+
+ Powered by %1$s
+
+
+ Share via
+
+ Erase browsing history?
+ Tap or clear this notification to securely erase your browsing history.
+
+
+ Tap or swipe this notification to securely erase your browsing history.
+
+ Erase browsing history
+
+
+ Open
+
+
+ Erase and Open
+
+
+ Erase
+
+
+ Erase browsing history
+
+
+
+ Erase & open
+
+
+ Erase and open %1$s
+
+
+
+ Search in Focus
+
+ Search in Klar
+
+
+ Search in Focus Beta
+
+ Search in Focus Nightly
+
+
+ %1$s puts you in control.
+Use it as a private browser:
+
+ Search and browse right in the app
+ Block trackers (or update settings to allow trackers)
+ Erase to delete cookies as well as search and browsing history
+
+
+%1$s is produced by Mozilla. Our mission is to foster a healthy, open Internet.
+Learn more
]]>
+
+
+ Privacy & Security
+
+
+ Tracking, cookies, data choices
+
+
+ Set default, autocomplete
+
+
+
+
+ About %1$s, help
+
+
+ Enhanced Tracking Protection
+
+
+ Web Content
+
+
+ Switching Apps
+
+
+ General
+
+
+ Default browser, language
+
+
+ Data Collection & Use
+
+ Search
+
+
+ Get search suggestions
+
+ %1$s will send what you type in the address bar to your search engine
+
+
+ Default
+
+
+ Search engine
+
+
+ On
+
+
+ Off
+
+
+ URL Autocomplete
+
+
+ For Top sites
+
+
+ Enable to have %s autocomplete over 450 popular URLs in the address bar.
+
+
+ For Sites You Add
+
+
+ Enable to have %s autocomplete your favourite URLs.
+
+
+ Manage sites
+
+
+ Manage sites
+
+
+ + Add custom URL
+
+
+ Your autocomplete list:
+
+
+ Add URL
+
+
+ Add custom URL
+
+
+ Add custom URL
+
+
+ Add link to autocomplete
+
+
+ Cookies and Site Data
+
+
+ Data Choices
+
+
+ Remove custom URLs
+
+
+ Learn more
+
+
+ Add and manage custom autocomplete URLs.
+
+
+ URL to add
+
+
+ Paste or enter URL
+
+
+ Example: mozilla.org
+
+
+ Example: example.com
+
+
+ New custom URL added.
+
+
+ Remove
+
+
+ Remove
+
+
+ Double-check the URL you entered.
+
+ Language
+
+ System default
+
+ Privacy
+ Block ad trackers
+ Some ads track site visits, even if you don’t click the ads
+ Block analytic trackers
+ Used to collect, analyze and measure activities like tapping and scrolling
+ Block social trackers
+ Embedded on sites to track your visits and to display functionality like share buttons
+ Block other content trackers
+ Enabling may cause some pages to behave unexpectedly
+ Block cookies
+
+
+ No thanks
+ Block 3rd-party tracker cookies only
+ Block 3rd-party cookies only
+
+ Block cross-site cookies
+ Yes please
+
+
+ Use fingerprint to unlock app
+
+
+ Unlock using fingerprint if you’ve added Shortcuts or when a website is already open in %s.
+
+
+ Stealth
+
+ Hide webpages when switching apps and block taking screenshots.
+
+ Security
+
+ Performance
+ Block Web fonts
+
+ May result in missing icons or images
+
+ Block JavaScript
+
+ Pages may load faster, but may also behave unexpectedly
+
+
+ Make %1$s default browser
+
+ Mozilla
+ Send usage data
+
+
+ Learn more
+
+
+ Mozilla strives to collect only what we need to provide and improve %1$s for everyone.
+
+
+ Privacy Notice
+
+
+ Licensing information
+
+
+ Libraries that we use
+
+
+ %s | OSS Libraries
+
+
+ About %1$s
+
+
+ Installed search engines
+
+
+ Choose search engine
+
+
+ Restore default search engines
+
+
+ + Add another search engine
+ Remove search engines
+ Remove
+
+
+ Add another search engine
+
+ Select your preferred engine:
+
+
+ Add search engine
+
+ Search engine name
+ Search string to use
+ Save
+
+
+ Example: example.com/search/?q=%s
+
+ New search engine added.
+
+ Enter search engine name
+ An installed search engine is already using that name.
+
+ Enter search string
+
+ Check that search string matches Example format
+
+
+ Clear input
+
+
+ Dismiss
+
+
+ Erase browsing history
+
+
+ Tabs open: %1$s
+
+
+ Secure connection
+
+
+ Loading
+
+
+ Website loaded
+
+
+ More options
+
+
+ More options button
+
+
+ Navigate forward
+
+
+ Reload website
+
+
+ Navigate back
+
+
+ Stop loading website
+
+
+ Return to previous app
+
+
+ Number of trackers blocked
+
+
+ Block trackers
+
+ Your Rights
+
+ Open link in another app
+
+ You can leave %1$s to open this link in %2$s.
+
+ Find an app that can open link
+
+ None of the apps on your device are able to open this link. You can leave %1$s to search %2$s for an app that can.
+
+ Exit Private Browsing?
+
+
+ %1$s finished
+
+
+ Open
+
+
+
+
+
+
+
+
+
+
+ Added to shortcuts!
+
+ Server not found
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+ Welcome to %1$s
+
+
+ Fast. Private. No distractions.
+
+
+ Get started
+
+
+
+ %1$s isn’t like other browsers
+
+
+ We clear your history when you close the app for extra privacy.
+
+
+
+ Make %1$s your default to protect your data with every link you open.
+
+
+ Set as default browser
+
+
+ Skip
+
+
+
+ Power up your privacy
+
+ Take private browsing to the next level. Block ads and other content that can track you across sites and bog down page load times.
+
+
+ Your search, your way
+
+ Searching for something different? Choose another default search engine in Settings.
+
+
+ Add shortcuts to your home screen
+
+ Return to your favourite sites in %1$s quickly. Just select “Add to Home screen” from the %1$s menu.
+
+
+ Make privacy a habit
+
+ Set %1$s as your default browser and get the benefits of private browsing when you open webpages from other apps.
+
+ OK, got it!
+ Skip
+ Next
+
+
+ -
+
+
+ Add
+
+
+ YES
+
+
+ Cancel
+
+
+ NO
+
+
+ Shortcut will open with Enhanced Tracking Protection disabled
+
+
+ Private browsing session
+
+
+ Notifications let you erase your %1$s session with a tap. You don’t need to open the app or see what’s running in your browser.
+
+
+ Erase browsing history
+
+
+ Download Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License and other open source licenses.]]>
+
+
+ here.]]>
+
+
+ licenses.]]>
+
+
+ GNU General Public License v3, and available here .]]>
+
+
+ Username
+ Password
+ Clear
+
+
+
+ Secure Connection
+ Insecure Connection
+
+ Verified by: %1$s
+
+
+ Site Security
+ URL already exists
+
+
+ Find in Page
+
+
+ Find in page
+
+
+ %1$d/%2$d
+
+ %1$d out of %2$d
+
+
+ Find next result
+
+ Find previous result
+
+ Dismiss find in page
+
+
+
+
+ Request desktop site
+
+
+ Desktop site
+
+
+ URL copied
+
+
+ Developer tools
+
+
+ Open links in apps
+
+
+ Advanced
+
+
+ Site permissions
+
+
+ Cookie Banner Reduction
+
+
+ On
+
+
+ Off
+
+
+ Cookie Banner Reduction
+
+
+ See fewer banners by automatically rejecting cookie requests, when possible.
+
+ -->
+ Cookie Banner Reduction
+
+
+ ON for this site
+
+
+ Site currently not supported
+
+
+ OFF for this site
+
+
+ Cookie Banner Reduction
+
+
+ OFF for this site
+
+
+ ON for this site
+
+
+ Turn on Cookie Banner Reduction for %1$s?
+
+
+ Turn off Cookie Banner Reduction for %1$s?
+
+
+ %1$s will clear this site’s cookies and refresh the page. Clearing all cookies may sign you out or empty shopping carts.
+
+
+ %1$s can try to automatically reject cookie requests.
+
+
+ This site is currently not supported by Cookie Banner Reduction. Would you like to request our team review this website and add support in the future?
+
+
+ Cancel
+
+
+ Request support
+
+
+ Request to support site submitted.
+
+
+ Request to support site submitted.
+
+
+
+ %1$s tries to reject cookie requests to dismiss annoying cookie banners.\n\nManage cookie banner preferences in %2$s.
+
+ settings
+
+
+ Autoplay
+
+
+ To allow it:
+
+
+ 1. Go to Android Settings
+
+
+ Permissions]]>
+
+
+ Go to Settings
+
+
+ %1$s to ON]]>
+
+
+ Camera
+
+
+ Microphone
+
+
+ Location
+
+
+ Notification
+
+
+ DRM-controlled content
+
+
+ Ask to allow
+
+
+ Blocked
+
+
+ Allowed
+
+
+ Blocked by Android
+
+
+ Allow audio and video
+
+
+ Block audio only
+
+
+ Recommended
+
+
+ Block audio and video
+
+
+ Studies
+
+
+ Firefox may install and run studies from time to time.
+
+
+ Learn more
+
+
+ The application will quit to apply changes
+
+
+ Remove
+
+
+ Active
+
+
+ Completed
+
+
+ Remote debugging via USB/Wi-Fi
+
+
+ Unlock
+
+
+ Confirm Using Your Fingerprint
+
+
+ You can use your fingerprint to continue your current app session.
+
+
+ Open Link in New Session
+
+
+ Fingerprint icon
+
+
+ Fingerprint not recognized. Try again.
+
+
+ Finger moved too fast. Try again.
+
+
+ Show search suggestions?
+
+
+ To get suggestions, %1$s needs to send what you type in the address bar to the search engine.
+
+
+ No
+
+
+ Yes
+
+
+ Some search engines cannot show suggestions.
+
+
+ Dismiss
+
+
+
+
+ Site behaving unexpectedly?\n Try turning off Tracking Protection
+
+
+ Add to Home screen]]>
+
+
+ Open every link in %1$s\n Set %1$s as default browser
+
+
+ Autocomplete URLs for sites you use most\n Long-press any URL in the address bar
+
+
+ Open a link in a new tab\n Long-press any link on a page
+
+
+ Turn off tips on the start screen
+
+
+ New tab opened
+
+
+ Switch
+
+
+ Entering full screen mode
+
+
+ Switch to link in new tab immediately
+
+
+ Block potentially dangerous and deceptive sites
+
+ Block reported deceptive and attack sites, malware sites, and unwanted software sites.
+
+
+ HTTPS-Only Mode
+
+
+ Automatically attempts to connect to sites using the HTTPS encryption protocol for increased security.
+
+
+ Exceptions
+
+ You have disabled Content Blocking for these websites.
+
+ Remove
+
+ Remove all websites
+
+
+ Block Cookies
+
+
+ Would you like to block cookies?
+
+
+ Tab Crashed
+
+ Sorry. We’re having a problem with this tab.
+
+ As a private browser, we never save and cannot restore this tab.
+
+ Close Tab
+
+
+
+
+
+ Send crash report to Mozilla
+
+
+
+
+ Trackers blocked since %s
+
+ Content
+
+ Advertising
+
+ Social
+
+ Analytics
+
+ Enhanced Tracking Protection
+
+ Protections are OFF for this site
+
+ Protections are ON for this site
+
+ Connection is secure
+
+
+ Connection is not secure
+
+ Trackers and Scripts to Block
+
+
+ Go back
+
+
+
+ Remove
+
+
+ Rename
+
+ Rename
+
+ Shortcut name
+
+
+ Saved and shared images <b>will not be</b> deleted when you erase %1$s history
+
+
+
+ Theme
+
+ Light
+
+ Dark
+
+ Set by Battery Saver
+
+ Follow device theme
+
+
+
+ This site doesn’t support HTTPS
+
+
+ Learn more
+ Change this setting in Settings > Privacy & Security > Security.]]>
+
+
+ Connection not secure
+
+
+
+ If you’ve connected to this server successfully in the past, the error may be temporary.
+ ]]>
+
+
+ Someone could be trying to impersonate the site and continuing could be risky.
+
+ %1$s does not trust %2$s because its certificate issuer is unknown, the certificate is self-signed, or the server is not sending the correct intermediate certificates.
+ ]]>
+
+
+
+ Close tab
+
+
+
+ Got ‘em! We stopped this site from spying on you. Tap the shield any time to see what we’re blocking.
+
+
+ Close popup
+
+
+
+ You’re protected!
+
+ These default settings offer strong protection. But it’s easy to tweak the settings to meet your specific needs.
+
+ Dismiss
+
+
+ Tap here to trash it all — history, cookies, everything — and start fresh on a new tab.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Close
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Search widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Browsing history cleared! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start your private browsing session, and we’ll block trackers and other bad stuff as you go.
+
+
+ We’ll leave you to your private browsing, but get a quicker start next time with the %1$s widget on your Home screen.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Add widget to home screen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget added to home screen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-en-rGB/strings.xml b/mobile/android/focus-android/app/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000000..48fb8ef313
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,1090 @@
+
+
+
+
+
+
+
+
+ Cancel
+
+ OK
+
+ Save
+
+
+ Search or enter address
+
+ Automatic private browsing.\nBrowse. Erase. Repeat.
+
+
+ Your browsing history has been erased.
+ Browsing history cleared
+
+
+ Tab’s browsing history has been erased.
+
+
+ Search for %1$s
+
+
+ Share…
+
+
+ Report Site Issue
+
+
+ Open in %1$s
+
+
+ Open in…
+
+
+ Add to Home screen
+
+
+ Add to Shortcuts
+
+ Remove from Shortcuts
+
+
+ Settings
+ About
+ Help
+ Your Rights
+
+
+ Trackers blocked
+
+
+ Turning this off may fix some site problems
+
+
+ Content Blocking
+
+ Turn off to fix some sites
+
+
+ Powered by %1$s
+
+
+ Share via
+
+ Erase browsing history?
+ Tap or clear this notification to securely erase your browsing history.
+
+
+ Tap or swipe this notification to securely erase your browsing history.
+
+ Erase browsing history
+
+
+ Open
+
+
+ Erase and Open
+
+
+ Erase
+
+
+ Erase browsing history
+
+
+
+ Erase & open
+
+
+ Erase and open %1$s
+
+
+
+ Search in Focus
+
+ Search in Klar
+
+ Search in Focus Beta
+
+ Search in Focus Nightly
+
+
+ %1$s puts you in control.
+Use it as a private browser:
+
+ Search and browse right in the app
+ Block trackers (or update settings to allow trackers)
+ Erase to delete cookies as well as search and browsing history
+
+
+%1$s is produced by Mozilla. Our mission is to foster a healthy, open Internet.
+Learn more
]]>
+
+
+ Privacy & Security
+
+
+ Tracking, cookies, data choices
+
+
+ Set default, autocomplete
+
+
+
+
+ About %1$s, help
+
+
+ Enhanced Tracking Protection
+
+
+ Web Content
+
+
+ Switching Apps
+
+
+ General
+
+
+ Default browser, language
+
+
+ Data Collection & Use
+
+ Search
+
+
+ Get search suggestions
+
+ %1$s will send what you type in the address bar to your search engine
+
+
+ Default
+
+
+ Search engine
+
+
+ On
+
+
+ Off
+
+
+ URL Autocomplete
+
+
+ For Top sites
+
+
+ Enable to have %s autocomplete over 450 popular URLs in the address bar.
+
+
+ For Sites You Add
+
+
+ Enable to have %s autocomplete your favourite URLs.
+
+
+ Manage sites
+
+
+ Manage sites
+
+
+ + Add custom URL
+
+
+ Your autocomplete list:
+
+
+ Add URL
+
+
+ Add custom URL
+
+
+ Add custom URL
+
+
+ Add link to autocomplete
+
+
+ Cookies and Site Data
+
+
+ Data Choices
+
+
+ Remove custom URLs
+
+
+ Learn more
+
+
+ Add and manage custom autocomplete URLs.
+
+
+ URL to add
+
+
+ Paste or enter URL
+
+
+ Example: mozilla.org
+
+
+ Example: example.com
+
+
+ New custom URL added.
+
+
+ Remove
+
+
+ Remove
+
+
+ Double-check the URL you entered.
+
+ Language
+
+ System default
+
+ Privacy
+ Block ad trackers
+ Some ads track site visits, even if you don’t click the ads
+ Block analytic trackers
+ Used to collect, analyse and measure activities like tapping and scrolling
+ Block social trackers
+ Embedded on sites to track your visits and to display functionality like share buttons
+ Block other content trackers
+ Enabling may cause some pages to behave unexpectedly
+ Block cookies
+
+
+ No thanks
+ Block 3rd-party tracker cookies only
+ Block 3rd-party cookies only
+ Block cross-site cookies
+ Yes please
+
+
+ Use fingerprint to unlock app
+
+
+ Unlock using fingerprint if you’ve added Shortcuts or when a web site is already open in %s.
+
+
+ Stealth
+
+ Hide web pages when switching apps and block taking screenshots.
+
+ Security
+
+ Performance
+ Block web fonts
+
+ May result in missing icons or images
+
+ Block JavaScript
+
+ Pages may load faster, but may also behave unexpectedly
+
+
+ Make %1$s default browser
+
+ Mozilla
+ Send usage data
+
+
+ Learn more
+
+
+ Mozilla strives to collect only what we need to provide and improve %1$s for everyone.
+
+
+ Privacy Notice
+
+
+ Licensing information
+
+
+ Libraries that we use
+
+
+ %s | OSS Libraries
+
+
+ About %1$s
+
+
+ Installed search engines
+
+
+ Choose search engine
+
+
+ Restore default search engines
+
+
+ + Add another search engine
+ Remove search engines
+ Remove
+
+ Add another search engine
+
+ Select your preferred engine:
+
+
+ Add search engine
+
+ Search engine name
+ Search string to use
+ Save
+
+
+ Example: example.com/search/?q=%s
+
+ New search engine added.
+
+ Enter search engine name
+ An installed search engine is already using that name.
+
+ Enter search string
+
+ Check that search string matches Example format
+
+
+ Clear input
+
+
+ Dismiss
+
+
+ Erase browsing history
+
+
+ Tabs open: %1$s
+
+
+ Secure connection
+
+
+ Loading
+
+
+ Web site loaded
+
+
+ More options
+
+
+ More options button
+
+
+ Navigate forwards
+
+
+ Reload web site
+
+
+ Navigate backwards
+
+
+ Stop loading web site
+
+
+ Return to previous app
+
+
+ Number of trackers blocked
+
+
+ Block trackers
+
+ Your Rights
+
+ Open link in another app
+
+ You can leave %1$s to open this link in %2$s.
+
+ Find an app that can open link
+
+ None of the apps on your device are able to open this link. You can leave %1$s to search %2$s for an app that can.
+
+ Exit Private Browsing?
+
+
+ %1$s finished
+
+
+ Open
+
+
+ Added to shortcuts!
+
+ Server not found
+
+
+ Close
+
+
+
+ Welcome to %1$s
+
+
+ Fast. Private. No distractions.
+
+
+ Get started
+
+
+
+ %1$s isn’t like other browsers
+
+
+ We clear your history when you close the app for extra privacy.
+
+
+
+ Make %1$s your default to protect your data with every link you open.
+
+
+ Set as default browser
+
+
+ Skip
+
+
+
+ Power up your privacy
+
+ Take private browsing to the next level. Block ads and other content that can track you across sites and bog down page load times.
+
+
+ Your search, your way
+
+ Searching for something different? Choose another default search engine in Settings.
+
+
+ Add shortcuts to your home screen
+
+ Return to your favourite sites in %1$s quickly. Just select \"Add to Home screen\" from the %1$s menu.
+
+
+ Make privacy a habit
+
+ Set %1$s as your default browser and get the benefits of private browsing when you open web pages from other apps.
+
+ OK, got it!
+
+ Skip
+ Next
+
+
+ -
+
+
+ Add
+
+ YES
+
+
+ Cancel
+
+ NO
+
+
+ Shortcut will open with Enhanced Tracking Protection disabled
+
+
+ Private browsing session
+
+
+ Notifications let you erase your %1$s session with a tap. You don’t need to open the app or see what’s running in your browser.
+
+
+ Erase browsing history
+
+
+ Download Firefox
+
+
+
+
+
+ Mozilla Public License and other open source licences.]]>
+
+
+ here.]]>
+
+
+ licences.]]>
+
+
+ GNU General Public Licence v3, and available here .]]>
+
+
+ Username
+ Password
+ Clear
+
+
+
+ Secure Connection
+ Insecure Connection
+
+ Verified by: %1$s
+
+
+ Site Security
+ URL already exists
+
+
+ Find in Page
+
+
+ Find in page
+
+
+ %1$d/%2$d
+
+ %1$d out of %2$d
+
+
+ Find next result
+
+ Find previous result
+
+ Dismiss find in page
+
+
+ Request desktop site
+
+
+ Desktop site
+
+
+ URL copied
+
+
+ Developer tools
+
+
+ Open links in apps
+
+
+ Advanced
+
+
+ Site permissions
+
+
+ Cookie Banner Reduction
+
+
+ On
+
+
+ Off
+
+
+ Cookie Banner Reduction
+
+
+ See fewer banners by automatically rejecting cookie requests, when possible.
+
+ -->
+ Cookie Banner Reduction
+
+
+ ON for this site
+
+
+ Site currently not supported
+
+
+ OFF for this site
+
+
+ Cookie Banner Reduction
+
+
+ OFF for this site
+
+
+ ON for this site
+
+
+ Turn on Cookie Banner Reduction for %1$s?
+
+
+ Turn off Cookie Banner Reduction for %1$s?
+
+
+ %1$s will clear this site’s cookies and refresh the page. Clearing all cookies may sign you out or empty shopping carts.
+
+
+ %1$s can try to automatically reject cookie requests.
+
+
+ This site is currently not supported by Cookie Banner Reduction. Would you like to request our team review this web site and add support in the future?
+
+
+ Cancel
+
+
+ Request support
+
+
+ Request to support site submitted.
+
+
+ Request to support site submitted.
+
+
+
+ %1$s tries to reject cookie requests to dismiss annoying cookie banners.\n\nManage cookie banner preferences in %2$s.
+
+
+ settings
+
+
+ Autoplay
+
+
+ To allow it:
+
+
+ 1. Go to Android Settings
+
+
+ Permissions]]>
+
+
+ Go to Settings
+
+
+ %1$s to ON]]>
+
+
+ Camera
+
+
+ Microphone
+
+
+ Location
+
+
+ Notification
+
+
+ DRM-controlled content
+
+
+ Ask to allow
+
+
+ Blocked
+
+
+ Allowed
+
+
+ Blocked by Android
+
+
+ Allow audio and video
+
+
+ Block audio only
+
+
+ Recommended
+
+
+ Block audio and video
+
+
+ Studies
+
+
+ Firefox may install and run studies from time to time.
+
+
+ Learn more
+
+
+ The application will quit to apply changes
+
+
+ Remove
+
+
+ Active
+
+
+ Completed
+
+
+ Remote debugging via USB/Wi-Fi
+
+
+ Unlock
+
+
+ Confirm Using Your Fingerprint
+
+
+ You can use your fingerprint to continue your current app session.
+
+
+ Open Link in New Session
+
+
+ Fingerprint icon
+
+
+ Fingerprint not recognised. Try again.
+
+
+ Finger moved too fast. Try again.
+
+
+ Show search suggestions?
+
+
+ To get suggestions, %1$s needs to send what you type in the address bar to the search engine.
+
+
+ No
+
+
+ Yes
+
+
+ Some search engines cannot show suggestions.
+
+
+ Dismiss
+
+
+
+
+ Site behaving unexpectedly?\n
+ Try turning off Tracking Protection
+
+
+ Add to Home screen]]>
+
+
+ Open every link in %1$s\n
+ Set %1$s as default browser
+
+
+
+ Autocomplete URLs for sites you use most\n
+ Long-press any URL in the address bar
+
+
+
+ Open a link in a new tab\n
+ Long-press any link on a page
+
+
+
+ Turn off tips on the start screen
+
+
+ New tab opened
+
+
+ Switch
+
+
+ Entering full screen mode
+
+
+ Switch to link in new tab immediately
+
+
+ Block potentially dangerous and deceptive sites
+
+ Block reported deceptive and attack sites, malware sites and unwanted software sites.
+
+
+ HTTPS-Only Mode
+
+
+ Automatically attempts to connect to sites using the HTTPS encryption protocol for increased security.
+
+
+ Exceptions
+
+ You have disabled content blocking for these web sites.
+
+ Remove
+
+ Remove all web sites
+
+
+ Block Cookies
+
+
+ Would you like to block cookies?
+
+
+ Tab Crashed
+
+ Sorry. We’re having a problem with this tab.
+
+ As a private browser, we never save and cannot restore this tab.
+
+ Close Tab
+
+
+ Send crash report to Mozilla
+
+
+
+
+ Trackers blocked since %s
+
+ Content
+
+ Advertising
+
+ Social
+
+ Analytics
+
+ Enhanced Tracking Protection
+
+ Protections are OFF for this site
+
+ Protections are ON for this site
+
+ Connection is secure
+
+ Connection is not secure
+
+ Trackers and Scripts to Block
+
+
+ Go backwards
+
+
+
+ Remove
+
+
+ Rename
+
+ Rename
+
+
+ Shortcut name
+
+
+ Saved and shared images <b>will not be</b> deleted when you erase %1$s history
+
+
+
+ Theme
+
+ Light
+
+ Dark
+
+ Set by Battery Saver
+
+ Follow device theme
+
+
+
+ This site doesn’t support HTTPS
+
+
+ Learn more
+ Change this setting in Settings > Privacy & Security > Security.]]>
+
+
+ Connection not secure
+
+
+
+ If you’ve connected to this server successfully in the past, the error may be temporary.
+ ]]>
+
+
+ Someone could be trying to impersonate the site and continuing could be risky.
+
+ %1$s does not trust %2$s because its certificate issuer is unknown, the certificate is self-signed, or the server is not sending the correct intermediate certificates.
+ ]]>
+
+
+
+ Close tab
+
+
+
+ Got ‘em! We stopped this site from spying on you. Tap the shield any time to see what we’re blocking.
+
+
+ Close popup
+
+
+
+ You’re protected!
+
+ These default settings offer strong protection. But it’s easy to tweak the settings to meet your specific needs.
+
+ Dismiss
+
+
+ Tap here to trash it all — history, cookies, everything — and start fresh on a new tab.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Close
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Search widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Browsing history cleared! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start your private browsing session, and we’ll block trackers and other bad stuff as you go.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ We’ll leave you to your private browsing, but get a quicker start next time with the %1$s widget on your Home screen.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Add widget to home screen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget added to home screen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-eo/strings.xml b/mobile/android/focus-android/app/src/main/res/values-eo/strings.xml
new file mode 100644
index 0000000000..4e0c38728b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-eo/strings.xml
@@ -0,0 +1,1118 @@
+
+
+
+
+
+
+
+
+ Nuligi
+
+ Akcepti
+
+ Konservi
+
+
+ Serĉu ion aŭ tajpu adreson
+
+ Aŭtomata privata retumo.\nRetumu. Viŝu. Ripetu.
+
+
+ Via retuma historio estis viŝita.
+
+ Retuma historio viŝita
+
+
+ La retuma historio de la langeto estis viŝita.
+
+
+ Serĉi %1$s
+
+
+ Dividi…
+
+
+ Raporti problemon en retejo
+
+
+ Malfermi per %1$s
+
+
+ Malfermi per…
+
+
+ Aldoni al hejmekrano
+
+
+ Aldoni al ŝparvojoj
+
+ Forigi el ŝparvojoj
+
+
+ Agordoj
+ Pri
+ Helpo
+ Viaj rajtoj
+
+
+ Blokitaj spuriloj
+
+
+ Malŝalto de tio ĉi povus solvi kelkajn retejajn problemojn
+
+
+ Blokado de enhavo
+
+ Malŝalti por ripari kelkajn retejojn
+
+
+ Kun teknologio de %1$s
+
+
+ Dividi per
+
+ Ĉu viŝi retuman historion?
+ Tuŝetu aŭ viŝu tiun ĉi sciigon por sekure viŝi vian retuman historion.
+
+
+ Tuŝetu aŭ forŝovu tiun ĉi sciigon por sekure viŝi vian retuman historion.
+
+ Forviŝi retuman historion
+
+
+ Malfermi
+
+
+ Forigi kaj malfermi
+
+
+ Forviŝi
+
+
+ Forviŝi retuman historion
+
+
+
+ Forigi kaj malfermi
+
+
+ Forigi kaj malfermi %1$s
+
+
+
+ Serĉi en Focus
+
+ Serĉi en Klar
+
+ Serĉi en Focus Beta
+
+ Serĉi en Focus Nightly
+
+
+ %1$s igas vin reganto.
+Uzu ĝin kiel privatan retumilon:
+
+ Serĉu kaj retumu rekte el la programo
+ Bloku spurilojn (aŭ agordi por permesi spurilojn)
+ Forigu kuketojn kaj ankaŭ retuman kaj serĉan historion
+
+
+%1$s estas farita de Mozilla. Nia misio estas stimuli sanan kaj malfermitan interreton.
+Pli da informo
]]>
+
+
+ Privateco kaj sekureco
+
+
+ Spurado, kuketoj, elekto de datumoj
+
+
+ Difini kiel norman, aŭtomata kompletigo
+
+
+
+
+ Pri %1$s, helpo
+
+
+ Plibonigita protekto kontraŭ spurado
+
+
+ Teksaĵa enhavo
+
+
+ Ŝanĝo de programoj
+
+
+ Ĝenerala
+
+
+ Norma retumilo, lingvo
+
+
+ Kolekto kaj uzo de datumoj
+
+ Serĉi
+
+
+ Ricevi serĉajn sugestojn
+
+ %1$s sendos tion, kion vi tajpas en la adresa strio, al via serĉilo
+
+
+ Norma
+
+
+ Serĉilo
+
+
+ Ŝaltita
+
+
+ Malŝaltita
+
+
+ Aŭtomata kompletigo de adresoj
+
+
+ Por la plej vizititaj retejoj
+
+
+ Aktivigu por igi %s aŭtomate kompletigi pli ol 450 popularajn adresojn en la adresa strio.
+
+
+ Por retejoj, kiujn vi aldonas
+
+
+ Akvitigi por ke %s aŭtomate kompletigu viajn plej ŝatatajn retadresojn.
+
+
+ Administri retejojn
+
+
+ Administri retejojn
+
+
+ + Aldoni personecigitan adreson
+
+
+ Via listo de aŭtomata plenigo:
+
+
+ Aldoni adreson
+
+
+ Aldoni personecigitan adreson
+
+
+ Aldoni personecigitan adreson
+
+
+ Aldoni aŭtomate plenigendan ligilon
+
+
+ Kuketoj kaj retejaj datumoj
+
+
+ Elekto de datumoj
+
+
+ Forigi personecigitajn adresojn
+
+
+ Pli da informo
+
+
+ Aldoni kaj administri aŭtomatan kompletigon de personecigitaj adresoj.
+
+
+ Aldonota adreso
+
+
+ Alglui aŭ tajpi adreson
+
+
+ Ekzemplo: mozilla.org
+
+
+ Ekzemplo: example.com
+
+
+ Nova personecigita adreso aldonita.
+
+
+ Forigi
+
+
+ Forigi
+
+
+ Kontrolu denove la tajpitan adreson.
+
+ Lingvo
+
+ Sistema normo
+
+ Privateco
+ Bloki reklamajn spurilojn
+ Kelkaj reklamoj spuras viajn vizitojn, eĉ se vi ne alklakas ilin
+ Bloki analizajn spurilojn
+ Ili estas uzataj por kolekti, analizi kaj mezuri agojn, kiel tuŝetojn kaj rulumojn
+ Bloki sociajn spurilojn
+ Inkluzivitaj en retejoj por spuri viajn vizitojn kaj montri funkciojn, kiel dividajn butonojn
+ Bloki spurilojn en aliaj enhavoj
+ Se vi ŝaltas tion ĉi, kelkaj paĝoj povus neatendite konduti
+ Bloki kuketojn
+
+
+ Ne, dankon
+ Bloki nur nerektajn spurajn kuketojn
+ Bloki nur nerektajn kuketojn
+
+ Bloki interretejajn kuketojn
+ Jes, bonvolu
+
+
+ Uzi fingrospuron por malbloki programon
+
+
+ Malbloki per fingrospuro se vi aldonis ŝparvojojn aŭ kiam retejo jam estas malfermita en %s.
+
+
+ Kaŝreĝimo
+
+ Kaŝi retpaĝojn dum ŝanĝo de programo kaj bloki la faradon de ekrankopioj.
+
+ Sekureco
+
+ Efikeco
+ Bloki retajn tiparojn
+
+ Tio povus malaperigi emblemojn aŭ bildojn
+
+ Bloki JavaScript
+
+ Paĝoj povas aperi pli rapide, sed ankaŭ povas konduti neatendite
+
+
+ Igi %1$s via norma retumilo
+
+ Mozilla
+ Sendi datumojn pri uzo
+
+
+ Pli da informo
+
+
+ Mozilla penas kolekti nur la datumojn nepre bezonatajn por provizi kaj plibonigi %1$s por ĉiuj.
+
+
+ Rimarko pri privateco
+
+
+ Permesila informo
+
+
+ Bibliotekoj uzataj de ni
+
+
+ %s | Malfermitkodaj bibliotekoj
+
+
+ Pri %1$s
+
+
+ Instalitaj serĉiloj
+
+
+ Elekti serĉilon
+
+
+ Remeti normajn serĉilojn
+
+
+ + Aldoni alian serĉilon
+ Forigi serĉilojn
+ Forigi
+
+
+ Aldoni alian serĉilon
+
+ Elektu vian preferatan serĉilon:
+
+
+ Aldoni serĉilon
+
+ Nomo de serĉilo
+ Teksto por serĉi
+ Konservi
+
+
+ Ekzemplo: example.com/search/?q=%s
+
+ Nova serĉilo aldonita.
+
+ Tajpu nomon de serĉilo
+ Tiu nomo jam estas uzita de instalita serĉilo.
+
+ Tajpu serĉan tekston
+
+ Kontrolu ĉu la serĉa teksto kongruas kun la formo de la ekzemplo
+
+
+ Viŝi kampon
+
+
+ Ignori
+
+
+ Viŝi retuman historion
+
+
+ Malfermitaj langetoj: %1$s
+
+
+ Sekura konekto
+
+
+ Ŝargado
+
+
+ Retejo ŝargita
+
+
+ Pli da elektebloj
+
+
+ Butono por montri pli da elektebloj
+
+
+ Iri antaŭen
+
+
+ Reŝargi retejon
+
+
+ Iri reen
+
+
+ Haltigi ŝargadon de retejo
+
+
+ Reen al antaŭa programo
+
+
+ Nombro de blokitaj spuriloj
+
+
+ Bloki spurilojn
+
+ Viaj rajtoj
+
+ Malfermi ligilon en alia programo
+
+ Vi povas forlasi %1$s por malfermi tiun ĉi ligilon en %2$s.
+
+ Serĉi programon, kiu povas malfermi ligilon
+
+ Neniu programo en via aparato povas malfermi tiun ĉi ligilon. Vi povas forlasi %1$s por serĉi en %2$s programon kiu povas fari tion.
+
+ Ĉu eliri el privata retumo?
+
+
+ %1$s finiĝis
+
+
+ Malfermi
+
+
+
+
+
+
+
+
+
+
+ Aldonita al ŝparvojoj!
+
+ Servilo ne trovita
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fermi
+
+
+
+ Bonvenon al %1$s
+
+
+ Rapida. Privata. Sendistra.
+
+
+ Unuaj paŝoj
+
+
+
+ %1$s ne estas kiel aliaj retumiloj
+
+
+ Ni viŝas vian historion kiam vi fermas la programon por havi kroman privatecon.
+
+
+
+ Igu %1$s via norma retumilo por protekti viajn datumojn en ĉiu malfermita ligilo.
+
+
+ Difini kiel norma retumilo
+
+
+ Ignori
+
+
+
+ Plibonigi vian privatecon
+
+ Privata retumo pli altnivela, kun blokado de reklamoj kaj aliaj enhavoj, kiuj povas spuri vin trans retejoj kaj malrapidigi la ŝargon de paĝoj.
+
+
+ Via serĉo laŭ via maniero
+
+ Ĉu vi serĉas ion alian, malsaman? Elektu alian norman serĉilon en la agordoj.
+
+
+ Aldoni rektajn alirojn al via hejmekrano
+
+ En %1$s, por reiri rapide al viaj plej ŝatataj retejoj, elektu \"Aldoni al hejmekrano\" en la menuo.
+
+
+ Alkutimiĝu al la privateco
+
+ Elektu %1$s kiel via normal retumilo kaj ricevu ĉiujn avantaĝojn de la privata retumo, kiam vi malfermas paĝojn el aliaj programoj.
+
+ En ordo, mi komprenis!
+ Ignori
+ Sekva
+
+
+ -
+
+
+ Aldoni
+
+ JES
+
+
+ Nuligi
+
+ NE
+
+
+ Tiu ĉi ŝparvojo estos malfermita sen plibonigita protekto kontraŭ spurado
+
+
+ Privata retuma seanco
+
+
+ Sciigoj permesas al vi viŝi vian seancon de %1$s per tuŝeto. Vi ne bezonas malfermi la programon aŭ vidi kio estas en via retumilo.
+
+
+ Viŝi retuman historion
+
+
+ Elŝuti Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License kaj de aliaj malfermitkodaj permesiloj.]]>
+
+
+ ĉi tie.]]>
+
+
+ permesiloj.]]>
+
+
+ GNU General Public License v3, kiu haveblas ĉi tie .]]>
+
+
+ Nomo de uzanto
+ Pasvorto
+ Viŝi
+
+
+
+ Sekura konekto
+ Nesekura konekto
+
+ Kontrolita de: %1$s
+
+
+ Sekureco de retejo
+ Retadreso jam ekzistas
+
+
+ Serĉi en paĝo
+
+
+ Serĉi en paĝo
+
+
+ %1$d/%2$d
+
+ %1$d el %2$d
+
+
+ Trovi venontan rezulton
+
+ Trovi antaŭan rezulton
+
+ Fermi serĉon en paĝo
+
+
+
+
+ Peti version por komputiloj
+
+
+ Skribotabla retejo
+
+
+ Retadreso kopiita
+
+
+ Iloj por programistoj
+
+
+ Malfermi ligilojn per programoj
+
+
+ Spertula
+
+
+ Permesoj por retejo
+
+
+ Redukto de kuketaj anoncoj
+
+
+ Ŝaltita
+
+
+ Malŝaltita
+
+
+ Redukto de kuketaj anoncoj
+
+
+ Montri malpli da anoncoj per aŭtomata rifuzo de kuketaj petoj, kiam tio eblas.
+
+ -->
+ Redukto de kuketaj anoncoj
+
+
+ Ŝaltita por tiu ĉi retejo
+
+
+ Retejo nuntempe ne subtenata
+
+
+ Malŝaltita por tiu ĉi retejo
+
+
+ Redukto de kuketaj anoncoj
+
+
+ Malŝaltita por tiu ĉi retejo
+
+
+ Ŝaltita por tiu ĉi retejo
+
+
+ Ĉu ŝalti la redukton de kuketaj anoncoj en %1$s?
+
+
+ Ĉu malŝalti la redukton de kuketaj anoncoj en %1$s?
+
+
+ %1$s forigos la kuketojn de tiuj ĉi retejo kaj reŝargos la paĝon. Forigo de ĉiuj kuketoj povas fini seancojn aŭ malplenigi aĉetumĉarojn.
+
+
+ %1$s povas aŭtomate rifuzi kuketajn petojn.
+
+
+ Tiu ĉi retejo ne estas subtenata de la redukto de kuketaj anoncoj. Ĉu vi ŝatus peti al ni revizii tiun ĉi retejon, por subteni ĝin estontece?
+
+
+ Nuligi
+
+
+ Peti subtenon
+
+
+ La peto pri subteno de retejo estis sendita.
+
+
+ La peto pri subteno de retejo estis sendita.
+
+
+
+ %1$s klopodas rifuzi kuketajn petojn por ignori ĝenajn anoncojn.\n\nAdministru viajn preferojn pri kuketaj anoncoj en %2$s.
+
+
+ agordoj
+
+
+ Aŭtomata ludado
+
+
+ Por permesi ion:
+
+
+ 1. Iru al Agordoj de Android
+
+
+ Permesoj]]>
+
+
+ Iri al agordoj
+
+
+ %1$s]]>
+
+
+ Filmilo
+
+
+ Mikrofono
+
+
+ Loko
+
+
+ Sciigoj
+
+
+ Enhavo protektita de DRM
+
+
+ Demandi antaŭ ol permesi
+
+
+ Blokita
+
+
+ Permesita
+
+
+ Blokita de Android
+
+
+ Permesi sonon kaj videon
+
+
+ Bloki nur sonon
+
+
+ Rekomenditaj
+
+
+ Bloki sonon kaj videon
+
+
+ Studoj
+
+
+ Firefox povas de tempo al tempo instali kaj fari studojn.
+
+
+ Pli da informo
+
+
+ Tiu ĉi programo finiĝos por apliki ŝanĝojn
+
+
+ Forigi
+
+
+ Aktiva
+
+
+ Kompleta
+
+
+ Fora senerarigo per USB aŭ sendrata reto
+
+
+ Malbloki
+
+
+ Konfirmi per via fingrospuro
+
+
+ Vi povas uzi vian fingrospuron por daŭrigi vian nunan sesion en la apo.
+
+
+ Malfermi ligilon en nova seancon
+
+
+ Emblemo de fingrospuro
+
+
+ Fingrospuro nerekonita. Provu denove.
+
+
+ La fingro moviĝis tro rapide. Provu denove.
+
+
+ Ĉu montri serĉajn sugestojn?
+
+
+ Por povi ion sugesti, %1$s devas sendi kion vi tajpas en la adresa strio al la serĉilo.
+
+
+ Ne
+
+
+ Jes
+
+
+ Kelkaj serĉiloj ne povas montri serĉajn sugestojn.
+
+
+ Ignori
+
+
+
+
+ Ĉu la retejo neatendite kondutas?\n Provu malŝalti la protekton kontraŭ spurado
+
+
+ Aldoni al hejmekrano]]>
+
+
+ Malfermi ĉiun ligilon per %1$s\n Igu %1$s via norma retumilo
+
+
+ Aŭtomate kompletigi restadresojn por la retejoj, kiujn vi plej multe uzas\n Longe premu iun ajn retadreson en la adresa strio
+
+
+ Malfermu ligilon en nova langeto\n Longe premu iun ajn ligilon en paĝo
+
+
+ Malŝalti konsilojn en la komenca ekrano
+
+
+ Nova langeto malfermita
+
+
+ Ŝanĝi
+
+
+ Plenekrana reĝimo aktiva
+
+
+ Iri tuj al la ligilo en nova langeto
+
+
+ Bloki verŝajne danĝeran kaj trompan enhavon
+
+ Bloki raportitajn trompajn, atakajn, fiprogramajn kaj trudprogramajn retejojn.
+
+
+ HTTPS-nura reĝimo
+
+
+ Aŭtomate klopodi konekti al retejoj per la ĉifrita HTTPS protokolo por pli sekura aliro.
+
+
+ Esceptoj
+
+ Vi malaktvigis la blokadon de enhavo por tiuj ĉi retejoj.
+
+ Forigi
+
+ Forigi ĉiujn retejojn
+
+
+ Bloki kuketojn
+
+
+ Ĉu vi ŝatus bloki kuketojn?
+
+
+ Langeto paneis
+
+ Bedaŭrinde ni havas problemojn kun tiu ĉi langeto.
+
+ Kiel privata retumilo, ni neniam konservis kaj do ne povas restarigi tiun ĉi langeton.
+
+ Fermi langeton
+
+
+
+
+
+ Sendi raporton pri paneo al Mozilla
+
+
+
+
+ Spuriloj blokitaj ekde %s
+
+ Enhavo
+
+
+ Reklamado
+
+ Socia
+
+ Statistikoj
+
+
+ Plibonigita protekto kontraŭ spurado
+
+ Protektoj malŝaltitaj por tiu ĉi retejo
+
+ Protektoj ŝaltitaj por tiu ĉi retejo
+
+ Sekura konekto
+
+ Nesekura konekto
+
+ Spuriloj kaj skriptoj blokendaj
+
+
+ Malantaŭen
+
+
+
+ Forigi
+
+ Renomi
+
+ Renomi
+
+
+ Nomo de ŝparvojo
+
+
+ Konservitaj kaj dividitaj bildoj <b>ne estos</b> forigitaj kiam vi viŝas la historion de %1$s
+
+
+
+ Etoso
+
+ Hela
+
+ Malhela
+
+
+ Difinita de la energiŝparilo
+
+ Uzi la etoson de la aparato
+
+
+ Tiu ĉi retejo ne subtenas HTTPS
+
+
+ Pli da informo
+ Ŝanĝu tiun ĉi agordon en Agordoj > Privateco kaj sekureco > Sekureco.]]>
+
+
+ Nesekura konekto
+
+
+
+ Se vi sukcese konektiĝis al tiu ĉi servilo en la pasinto, la eraro povas esti momenta. ]]>
+
+
+ Iu povus voli trompe aspekti kiel tiu retejo, kaj daŭrigo estas riska.
+
+%1$s ne fidas %2$s ĉar la eldoninto de ĝia atestilo estas nekonata, la atestilo estas memsubskribita aŭ la servilo ne sendas la ĝustajn perajn atestilojn.
+]]>
+
+
+
+ Fermi langeton
+
+
+
+ Kaptita! Ni evitis ke tiu ĉi retejo spionu vin. Tuŝetu la ŝildon iam ajn por vidi kion ni blokas.
+
+
+ Fermi ŝprucaĵon
+
+
+
+ Vi estas protektata!
+
+ Tiuj ĉi normaj agordoj provizas fortan protekton. Sed estas facile modifi ilin por viaj specifaj bezonoj.
+
+ Ignori
+
+
+ Tuŝetu ĉi tie por forigi ĉion — historion, kuketojn, ĉion — kaj komenci je nulo en nova langeto.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Fermi
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Serĉila komponanto
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Retuma historio viŝita! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Komencu vian seancon de privata retumo kaj ni blokos spurilojn kaj aliajn aĉaĵojn dum vi retumas.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Plu retumu private, sed venontfoje komencu tion pli rapide per komponanto de %1$s en via hejmekrano.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Aldoni komponanton al hejmekrano
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Komponanto aldonita al hejmekrano
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-es-rAR/strings.xml b/mobile/android/focus-android/app/src/main/res/values-es-rAR/strings.xml
new file mode 100644
index 0000000000..02222fab8c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-es-rAR/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ Cancelar
+
+ Aceptar
+
+ Guardar
+
+
+ Buscar o ingresar dirección
+
+ Navegación privada automática.\nNavegar. Borrar. Repetir.
+
+
+ Se borró el historial de navegación.
+ Historial de navegación borrado
+
+
+ Se borró el historial de navegación de la pestaña.
+
+
+ Buscar %1$s
+
+
+ Compartir…
+
+
+ Informar sobre problema en el sitio
+
+
+ Abrir en %1$s
+
+
+ Abrir en…
+
+
+ Agregar a pantalla de inicio
+
+
+ Agregar acceso directo
+
+ Eliminar acceso directo
+
+
+ Configuración
+ Acerca de
+ Ayuda
+ Tus derechos
+
+
+ Rastreadores bloqueados
+
+
+ Desactivar esto puede solucionar algunos problemas del sitio
+
+
+ Bloqueo de contenido
+
+ Desactivar para corregir algunos sitios
+
+
+ Con tecnología de %1$s
+
+
+ Compartir vía
+
+ ¿Borrar historial de navegación?
+ Tocá o borrá esta notificación para eliminar de forma segura tu historial de navegación.
+
+
+ Tocá o deslizá esta notificación para eliminar de forma segura tu historial de navegación.
+
+ Borrar historial de navegación
+
+
+ Abrir
+
+
+ Borrar y abrir
+
+
+ Borrar
+
+
+ Borrar historial de navegación
+
+
+
+ Eliminar y abrir
+
+
+ Eliminar y abrir %1$s
+
+
+
+ Buscar en Focus
+
+ Buscar en Klar
+
+ Buscar en Focus Beta
+
+ Buscar en Focus Nightly
+
+
+ %1$s te da el control.
+Usalo como navegador privado:
+
+ Buscar y navegar directamente en la aplicación
+ Bloquear rastreadores (o actualizar los ajustes para permitir rastreadores)
+ Borrar para eliminar cookies e historial de búsqueda y navegación
+
+
+%1$s es producido por Mozilla. Nuestra misión es promover una Internet saludable y abierta.
+Conocer más
]]>
+
+
+ Privacidad y seguridad
+
+
+ Rastreo, cookies, opciones de datos
+
+
+ Predeterminados, autocompletar
+
+
+
+
+ Acerca de %1$s, ayuda
+
+
+ Protección de rastreo aumentada
+
+
+ Contenido web
+
+
+ Intercambio de aplicaciones
+
+
+ General
+
+
+ Navegador predeterminado, idioma
+
+
+ Recolección de datos y uso
+
+ Buscar
+
+
+ Obtener sugerencias de búsqueda
+
+ %1$s enviará lo que ingreses en la barra de direcciones a tu motor de búsqueda
+
+
+ Predeterminado
+
+
+ Buscador
+
+
+ Activado
+
+
+ Desactivado
+
+
+ Autocompletar URL
+
+
+ Para los sitios más visitados
+
+
+ Habilitar para que %s autocomplete más de 450 URLs populares en la barra de direcciones.
+
+
+ Para los sitios que agrega
+
+
+ Habilitar para que %s autocomplete tus URLs favoritas.
+
+
+ Administrar sitios
+
+
+ Administrar sitios
+
+
+ + Agregar URL personalizada
+
+
+ Tu lista de autocompletado:
+
+
+ Agregar URL
+
+
+ Agregar URL personalizada
+
+
+ Agregar URL personalizada
+
+
+ Agregar el enlace para autocompletar
+
+
+ Cookies y datos del sitio
+
+
+ Elección de datos
+
+
+ Borrar URL personalizada
+
+
+ Conocer más
+
+
+ Agregar y administrar URLs personalizadas.
+
+
+ URL para agregar
+
+
+ Pegue o escriba una URL
+
+
+ Ejemplo: mozilla.org
+
+
+ Ejemplo: ejemplo.com
+
+
+ Nueva URL personalizada agregada.
+
+
+ Borrar
+
+
+ Borrar
+
+
+ Verifique la URL que escribió.
+
+ Idioma
+
+ Predeterminado del sistema
+
+ Privacidad
+ Bloquear rastreadores de publicidad
+ Algunas publicidades rastrean visitas a los sitios aunque no haga clic en los anuncios
+ Bloquear rastreadores analíticos
+ Se usan para recolectar, analizar y medir actividades como tocar y desplazar
+ Bloquear rastreadores sociales
+ Incorporados en sitios para rastrear visitas y mostrar funcionalidades como los botones de compartir
+ Bloquear otros rastreadores de contenidos
+ Habilitarlos puede causar que algunas páginas se comporten de forma inesperada
+ Bloquear las cookies
+
+
+ No, gracias
+ Bloquear solo cookies de rastreo de terceros
+ Bloquear sólo cookies de terceros
+
+ Bloquear cookies de sitios cruzados
+ Sí, por favor
+
+
+ Usar la huella digital de para desbloquear la aplicación
+
+
+ Desbloqueá usando la huella digital si agregaste accesos directos o cuando un sitio web ya está abierto en %s.
+
+
+ Invisible
+
+ Ocultar páginas web al intercambiar aplicaciones y bloquear la toma de capturas de pantalla.
+
+ Seguridad
+
+ Rendimiento
+ Bloquear fuentes web
+
+ Puede resultar en la falta de iconos o imágenes
+
+ Bloquear JavaScript
+
+ Las páginas podrán cargar más rápido, pero también pueden comportarse de manera inesperada
+
+
+ Establecer %1$s como navegador predeterminado
+
+ Mozilla
+ Enviar datos de uso
+
+
+ Conocer más
+
+
+ Mozilla lucha por recolectar solo lo necesario para brindar y mejorar %1$s para todos.
+
+
+ Aviso de privacidad
+
+
+ Información de licencia
+
+
+ Bibliotecas que usamos
+
+
+ %s | Bibliotecas OSS
+
+
+ Acerca de %1$s
+
+
+ Motores de búsqueda instalados
+
+
+ Elegir buscador
+
+
+ Restaurar los motores de búsqueda predeterminados
+
+
+ +Agregar otro buscador
+ Eliminar los motores de búsqueda
+ Eliminar
+
+
+ Agregar otro buscador
+
+ Elegí tu buscador preferido:
+
+
+ Agregar buscador
+
+ Nombre del buscador
+ Cadena de búsqueda para usar
+ Guardar
+
+
+ Ejemplo: example.com/search/?q=%s
+
+ Se agregó un buscador nuevo.
+
+ Ingresar el nombre del buscador
+ Ya hay instalado un buscador con ese nombre.
+
+ Ingresar cadena de búsqueda
+
+ Verificar que la cadena de búsqueda coincida con el formato del ejemplo
+
+
+ Borrar entrada
+
+
+ Descartar
+
+
+ Limpiar historial de navegación
+
+
+ Pestañas abiertas: %1$s
+
+
+ Conexión segura
+
+
+ Cargando
+
+
+ Sitio web cargado
+
+
+ Más opciones
+
+
+ Botón de más opciones
+
+
+ Navegar hacia adelante
+
+
+ Recargar sitio web
+
+
+ Navegar hacia atrás
+
+
+ Dejar de cargar el sitio web
+
+
+ Volver a la aplicación anterior
+
+
+ Número de rastreadores bloqueados
+
+
+ Bloquear rastreadores
+
+ Tus derechos
+
+ Abrir enlace en otra aplicación
+
+ Puede dejar que %1$s abra este enlace en %2$s.
+
+ Buscar una app que pueda abrir el enlace
+
+ Ninguna de las apps de tu dispositivo puede abrir este enlace. Podés dejar que %1$s busque en %2$s una app compatible.
+
+ ¿Salir de la navegación privada?
+
+
+ %1$s finalizado
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+
+ ¡Agregado a accesos directos!
+
+ Servidor no encontrado
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cerrar
+
+
+
+ Bienvenido a %1$s
+
+
+ Rápido. Privado. Sin distracciones.
+
+
+
+ Comenzar
+
+
+
+ %1$s no es como los otros navegadores
+
+
+ Borramos tu historial cuando cerrás la aplicación para mayor privacidad.
+
+
+
+ Poné a %1$s como predeterminado para proteger tus datos con cada enlace que abrás.
+
+
+ Establecer como navegador predeterminado
+
+
+ Omitir
+
+
+
+ Potenciá tu privacidad
+
+ Lleva la navegación privada al siguiente nivel. Bloquéa publicidades y otro contenido que puede rastrearte en todos los sitios y lentificar los tiempos de carga de las páginas.
+
+
+ Tu búsqueda, a tu manera
+
+ ¿Buscás algo diferente? Elegí otro buscador predeterminado en la configuración.
+
+
+ Agregar accesos directos a la pantalla principal
+
+ Volvé a tus sitios favoritos en %1$s rápidamente. Seleccioná \"Agregar a pantalla de inicio\" en el menú de %1$s.
+
+
+ Hacé de la privacidad un hábito
+
+ Establecé %1$s como navegador predeterminado y obtené los beneficios de la navegación privada cuando abras páginas web desde otras aplicaciones.
+
+ Genial, ¡lo tengo!
+ Saltar
+ Siguiente
+
+
+ -
+
+
+ Agregar
+
+
+ SÍ
+
+
+ Cancelar
+
+
+ NO
+
+
+ El acceso directo se abrirá con la protección de rastreo aumentada desactivada
+
+
+ Sesión de navegación privada
+
+
+ Las notificaciones te permiten borrar la sesión de %1$s con un toque. No necesitás abrir la aplicación o ver lo que está corriendo en el navegador.
+
+
+ Borrar historial de navegación
+
+
+ Descargar Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License y otras licencias de código abierto.]]>
+
+
+ aquí.]]>
+
+
+ licencias gratuitas y de código abierto.]]>
+
+
+ GPLv3 (GNU General Public License v3), y se encuentra disponible aquí .]]>
+
+
+ Nombre de usuario
+ Contraseña
+ Eliminar
+
+
+
+ Conexión segura
+ Conexión insegura
+
+ Verificado por: %1$s
+
+
+ Seguridad del sitio
+ Ya existe la URL
+
+
+ Buscar en la página
+
+
+ Buscar en la página
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Buscar próximo resultado
+
+ Buscar resultado anterior
+
+ Cerrar búsqueda en la página
+
+
+
+
+ Pedir versión de escritorio
+
+
+ Sitio de escritorio
+
+
+ URL copiada
+
+
+ Herramientas para desarrolladores
+
+
+ Abrir enlaces en aplicaciones
+
+
+ Avanzadas
+
+
+ Permisos del sitio
+
+
+ Reducción de mensajes de cookies
+
+
+ Activada
+
+
+ Desactivada
+
+
+ Reducción de mensajes de cookies
+
+
+ Vea menos anuncios rechazando automáticamente las solicitudes de cookies cuando sea posible.
+
+ -->
+ Reducción de mensajes de cookies
+
+
+ ACTIVADA para este sitio
+
+
+ El sitio actualmente no es compatible
+
+
+ DESACTIVADA para este sitio
+
+
+ Reducción de mensajes de cookies
+
+
+ DESACTIVADA para este sitio
+
+
+ ACTIVADA para este sitio
+
+
+ ¿Habilitar reducción de mensajes de cookies para %1$s?
+
+
+ ¿Deshabilitar reducción de mensajes de cookies para %1$s?
+
+
+ %1$s borrará las cookies de este sitio y actualizará la página. Borrar todas las cookies puede cerrar la sesión o vaciar los carritos de compras.
+
+
+ %1$s puede intentar rechazar automáticamente las solicitudes de cookies.
+
+
+ Este sitio actualmente no soporta la reducción de mensajes de cookies. ¿Querés pedirle a nuestro equipo que revise este sitio web y agregue soporte en el futuro?
+
+
+ Cancelar
+
+
+ Pedir soporte
+
+
+ Pedido para soporte del sitio enviado.
+
+
+ Pedido para soporte del sitio enviado.
+
+
+
+ %1$s trata de rechazar los pedidos de cookies para descartar mensajes de cookies molestos.\n\nAdministrar preferencias de mensajes de cookies en %2$s.
+
+
+ ajustes
+
+
+ Reproducción automática
+
+
+ Para permitirlo:
+
+
+ 1. Ir a Ajustes de Android
+
+
+ Permisos]]>
+
+
+ Ir a Ajustes
+
+
+ %1$s a Habilitado]]>
+
+
+ Cámara
+
+
+ Micrófono
+
+
+ Ubicación
+
+
+ Notificación
+
+
+ Contenido controlado por DRM
+
+
+ Pedir permiso
+
+
+ Bloqueado
+
+
+ Permitido
+
+
+ Bloqueado por Android
+
+
+ Permitir audio y video
+
+
+ Bloquear solo audio
+
+
+ Recomendado
+
+
+ Bloquear audio y video
+
+
+ Estudios
+
+
+ Firefox puede instalar y ejecutar estudios de vez en cuando.
+
+
+ Conocer más
+
+
+ La aplicación se cerrará para aplicar los cambios
+
+
+ Eliminar
+
+
+ Activo
+
+
+ Completo
+
+
+ Depuración remota vía USB/Wi-Fi
+
+
+ Desbloquear
+
+
+ Confirmar usando tu huella digital
+
+
+ Podés usar tu huella digital para continuar con la sesión actual de la aplicación.
+
+
+ Abrir enlace en una nueva sesión
+
+
+ Icono de la huella digital
+
+
+ Huella digital no reconocida. Pruebe de nuevo.
+
+
+ El dedo se movió muy rápido. Pruebe de nuevo.
+
+
+ ¿Mostrar sugerencias de búsqueda?
+
+
+ Para obtener sugerencias de búsqueda, %1$s necesita enviar lo que ingreses en la barra de direcciones al motor de búsqueda.
+
+
+ No
+
+
+ Sí
+
+
+ Algunos motores de búsqueda no pueden mostrar sugerencias.
+
+
+ Descartar
+
+
+
+
+ ¿El sitio se comporta de manera inesperada?\n Pruebe desactivar la Protección de rastreo
+
+
+ Añadir a la Pantalla de inicio]]>
+
+
+ Abrir todos los enlaces en %1$s\n Configure %1$s como su navegador predeterminado
+
+
+ Autocomplete las URLs para los sitios que más usa\n Mantenga presionada cualquier URL en la barra de direcciones
+
+
+ Abra un enlace en una nueva pestaña\n Mantenga presionado cualquier enlace en una página
+
+
+ Desactivar los consejos en la pantalla de inicio
+
+
+ Nueva pestaña abierta
+
+
+ Cambiar
+
+
+ Cambiando a pantalla completa
+
+
+ Cambiar a enlazar en nueva pestaña inmediatamente
+
+
+ Bloquear sitios potencialmente peligrosos y engañosos
+
+ Bloquear los sitios de ataque y engaños informados, sitios de aplicaciones dañinas y sitios de software no deseados.
+
+
+ Modo solo HTTPS
+
+
+ Intenta conectarse automáticamente a sitios usando el protocolo de cifrado HTTPS para mayor seguridad.
+
+
+ Excepciones
+
+ Desactivó el Bloqueo de contenido para estos sitios.
+
+ Eliminar
+
+ Eliminar todos los sitios
+
+
+ Bloquear cookies
+
+
+ ¿Te gustaría bloquear las cookies?
+
+
+ Falló la pestaña
+
+ Disculpe. Tenemos un problema con esta pestaña.
+
+ Como navegador privado, nunca guardamos y no podemos restaurar esta pestaña.
+
+ Cerrar pestaña
+
+
+
+
+
+ Enviar informe de fallo a Mozilla
+
+
+
+
+ Rastreadores bloqueados desde %s
+
+ Contenido
+
+ Publicidad
+
+ Social
+
+ Analítica
+
+ Protección de rastreo aumentada
+
+ Las protecciones están desactivadas para este sitio
+
+ Las protecciones están activadas para este sitio
+
+ La conexión es segura
+
+ La conexión no es segura
+
+
+ Rastreadores y scripts a bloquear
+
+
+ Ir atrás
+
+
+
+ Eliminar
+
+
+ Renombrar
+
+ Renombrar
+
+
+ Nombre del atajo
+
+
+ Las imágenes guardadas y compartidas <b>no serán</b> borradas al borrar el historial de %1$s
+
+
+
+ Tema
+
+ Claro
+
+ Oscuro
+
+ Establecido por ahorro de batería
+
+ Usar el tema del dispositivo
+
+
+
+ Este sitio no soporta HTTPS
+
+
+ Conocer más
+ Se puede cambiar en Configuración > Privacidad y seguridad > Seguridad.]]>
+
+
+ Conexión insegura
+
+
+
+ Si te conectaste a este servidor exitosamente en el pasado, el error puede ser temporal.
+]]>
+
+
+ Alguien podría estar intentando imitar el sitio y continuar podría ser riesgoso.
+
+ %1$s no confía en %2$s porque el emisor del certificado es desconocido, el certificado está autofirmado o el servidor no envía los certificados intermedios correctos.
+ ]]>
+
+
+
+ Cerrar pestaña
+
+
+
+ ¡Excelente! Evitamos que esta página web te espíe. Presioná el escudo para ver lo que estamos bloqueando.
+
+
+ Cerrar ventana emergente
+
+
+
+ ¡Estás protegido!
+
+ Esta configuración predeterminada ofrece una protección fuerte. Pero es fácil modificar la configuración para satisfacer tus necesidades específicas.
+
+ Descartar
+
+
+ Tocá aquí para eliminarlo todo: historial, cookies, absolutamente todo y empezá de cero en una nueva pestaña.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Cerrar
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget de búsqueda
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ¡Historial de navegación borrado! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Iniciá tu sesión de navegación privada y bloquearemos rastreadores y otras amenazas mientras navegás.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Te dejaremos con tu navegación privada, pero podés conseguir un inicio más rápido la próxima vez con el widget de %1$s en la pantalla de inicio.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Agregar widget a la pantalla de inicio
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget agregado a la pantalla de inicio
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-es-rCL/strings.xml b/mobile/android/focus-android/app/src/main/res/values-es-rCL/strings.xml
new file mode 100644
index 0000000000..69b3b0c4e8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-es-rCL/strings.xml
@@ -0,0 +1,1117 @@
+
+
+
+
+
+
+
+
+ Cancelar
+
+ Aceptar
+
+ Guardar
+
+
+ Buscar o ingresar dirección
+
+ Navegación privada automática.\nBuscar. Borrar. Repetir.
+
+
+ Tu historial de navegación ha sido limpiado.
+ Historial de navegación limpiado
+
+
+ El historial de navegación de la pestaña ha sido limpiado.
+
+
+ Buscar por %1$s
+
+
+ Compartir…
+
+
+ Reportar problema con el sitio
+
+
+ Abrir en %1$s
+
+
+ Abrir en…
+
+
+ Añadir a la pantalla de inicio
+
+
+ Añadir a accesos directos
+
+ Eliminar de los accesos directos
+
+
+ Ajustes
+ Acerca de
+ Ayuda
+ Tus derechos
+
+
+ Rastreadores bloqueados
+
+
+ Desactivar esto puede solucionar algunos problemas del sitio
+
+
+ Bloqueo de contenido
+
+ Desactivar para arreglar algunos sitios
+
+
+ Impulsado por %1$s
+
+
+ Compartir vía
+
+ ¿Eliminar historial de navegación?
+ Toca o elimina esta notificación para borrar de forma segura tu historial de navegación.
+
+
+ Toca o desliza esta notificación para borrar de forma segura tu historial de navegación.
+
+ Eliminar historial de navegación
+
+
+ Abrir
+
+
+ Borrar y abrir
+
+
+ Eliminar
+
+
+ Eliminar historial de navegación
+
+
+
+ Eliminar y abrir
+
+
+ Eliminar y abrir %1$s
+
+
+
+ Buscar en Focus
+
+ Buscar en Klar
+
+ Buscar en Focus Beta
+
+ Buscar en Focus Nightly
+
+
+ %1$s te pone al control.
+Úsalo como un navegador privado:
+
+ Busca y navega directo desde la app
+ Bloquea rastreadores (o cambia los ajustes para permitirlos)
+ Mantente libre de cookies y del historial de búsqueda y navegación
+
+
+%1$s es producido por Mozilla. Nuestra misión es fomentar un Internet sano y abierto.
+Aprender más
]]>
+
+
+ Privacidad y seguridad
+
+
+ Seguimiento, cookies, selección de datos
+
+
+ Predeterminados, autocompletar
+
+
+
+
+ Acerca de %1$s, ayuda
+
+
+ Protección de seguimiento mejorada
+
+
+ Contenido Web
+
+
+ Cambio de aplicación
+
+
+ General
+
+
+ Navegador predeterminado, idioma
+
+
+ Recolección de datos y uso
+
+ Buscar
+
+
+ Recibir sugerencias de búsqueda
+
+ %1$s enviará lo que escribas en la barra de direcciones a tu motor de búsqueda
+
+
+ Predeterminado
+
+
+ Motor de búsqueda
+
+
+ Activado
+
+
+ Desactivado
+
+
+ Autocompletar URL
+
+
+ Para los sitios frecuentes
+
+
+ Activar para que %s autocomplete más de 450 URLs populares en la barra de direcciones.
+
+
+ Para los sitios que añadas
+
+
+ Activar para que%s autocomplete tus URLs favoritas.
+
+
+ Gestionar sitios
+
+
+ Gestionar sitios
+
+
+ + Añadir URL personalizada
+
+
+ Tu lista de autocompletado:
+
+
+ Añadir URL
+
+
+ Añadir URL personalizada
+
+
+ Añadir URL personalizada
+
+
+ Agregar el enlace para autocompletar
+
+
+ Cookies y datos de sitio
+
+
+ Selección de datos
+
+
+ Eliminar URLs personalizadas
+
+
+ Aprender más
+
+
+ Añadir y gestionar autocompletado de URL personalizadas.
+
+
+ URL a añadir
+
+
+ Pegar o ingresar URL
+
+
+ Ejemplo: mozilla.org
+
+
+ Ejemplo: example.com
+
+
+ Nueva URL personalizada añadida.
+
+
+ Eliminar
+
+
+ Eliminar
+
+
+ Verifica la URL que ingresaste.
+
+ Idioma
+
+ Predeterminado del sistema
+
+ Privacidad
+ Bloquear rastreadores de publicidad
+ Algunas avisos publicitarios rastrean las visitas a sitios, incluso si no haces clic en ellos
+ Bloquear rastreadores de analíticas
+ Son usados para recolectar, analizar y medir actividades como los toques o los desplazamientos
+ Bloquear rastreadores sociales
+ Los incrustan en sitios para rastrear tus visitas y mostrar funcionalidades como los botones para compartir
+ Bloquear otros rastreadores de contenido
+ Activar esto puede hacer que algunas páginas se comporten de manera inesperada
+ Bloquear cookies
+
+
+ No, gracias
+ Bloquear solo cookies de rastreo de terceros
+ Bloquear solo cookies de terceros
+
+ Bloquear cookies de sitios cruzados
+ Sí, por favor
+
+
+ Usar la huella digital para desbloquear la aplicación
+
+
+ Desbloquea usando la huella digital si añadiste accesos directos o cuando un sitio web ya está abierto en %s.
+
+
+ Sigiloso
+
+ Ocultar páginas web al cambiar de aplicación y bloquear la toma de capturas de pantalla
+
+ Seguridad
+
+ Rendimiento
+ Bloquear fuentes Web
+
+ Puede resultar en íconos o imágenes faltantes
+
+ Bloquear JavaScript
+
+ Las páginas pueden cargar más rápido, pero también pueden comportarse inesperadamente
+
+
+ Establecer %1$s como el navegador predeterminado
+
+ Mozilla
+ Enviar datos de uso
+
+
+ Aprender más
+
+
+ Mozilla se esfuerza por recolectar solo lo que necesitama para proporcionar y mejorar %1$s para todos.
+
+
+ Aviso de privacidad
+
+
+ Información de licencia
+
+
+ Bibliotecas que utilizamos
+
+
+ %s | Bibliotecas OSS
+
+
+ Acerca de %1$s
+
+
+ Motores de búsqueda instalados
+
+
+ Elige un motor de búsqueda
+
+
+ Restaurar motores de búsqueda predeterminados
+
+
+ + Añadir otro motor de búsqueda
+ Eliminar motores de búsqueda
+ Eliminar
+
+
+ Añade otro motor de búsqueda
+
+ Selecciona tu motor preferido:
+
+
+ Añadir motor de búsqueda
+
+ Nombre del motor de búsqueda
+ Cadena de búsqueda a usar
+ Guardar
+
+
+ Ejemplo: example.com/search/?q=%s
+
+ Nuevo motor de búsqueda añadido.
+
+ Ingresa el nombre del motor de búsqueda
+ Un motor de búsqueda instalado ya está usando ese nombre.
+
+ Ingresa la cadena de búsqueda
+
+ Revisa que la cadena de búsqueda coincida con el formato del ejemplo
+
+
+ Limpiar texto ingresado
+
+
+ Ocultar
+
+
+ Eliminar historial de navegación
+
+
+ Pestañas abiertas: %1$s
+
+
+ Conexión segura
+
+
+ Cargando
+
+
+ Sitio cargado
+
+
+ Más opciones
+
+
+ Botón de más opciones
+
+
+ Navegar hacia adelante
+
+
+ Recargar sitio
+
+
+ Navegar hacia atrás
+
+
+ Dejar de cargar el sitio
+
+
+ Regresar a la app anterior
+
+
+ Número de rastreadores bloqueados
+
+
+ Bloquear rastreadores
+
+ Tus derechos
+
+ Abrir enlace en otra app
+
+ Puedes salir de %1$s para abrir este enlace en %2$s.
+
+ Buscar una app que pueda abrir el enlace
+
+ Ninguna de las aplicaciones en tu dispositivo puede abrir este enlace. Puedes salir de %1$s para buscar en %2$s por una app que pueda.
+
+ ¿Salir de la navegación privada?
+
+
+ %1$s finalizado
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+
+ ¡Añadido a accesos directos!
+
+ Servidor no encontrado
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cerrar
+
+
+
+ Te damos la bienvenida a %1$s
+
+
+ Rápido. Privado. Sin distracciones.
+
+
+ Empezar
+
+
+
+ %1$s no es como otros navegadores
+
+
+ Borramos tu historial cuando cierras la aplicación para mayor privacidad.
+
+
+
+ Establece %1$s como tu predeterminado para proteger tus datos con cada enlace que abras.
+
+
+ Establecer como navegador predeterminado
+
+
+ Saltar
+
+
+
+ Potencia tu privacidad
+
+ Lleva la navegación privada al siguiente nivel. Bloquea anuncios publicitarios y otro tipo de contenido que puede rastrearte a través de los sitios y ralentizar los tiempos de carga de las páginas.
+
+
+ Tu búsqueda, a tu manera
+
+ ¿Buscas algo diferente? Elige otro motor de búsqueda predeterminado en los ajustes.
+
+
+ Añade atajos a tu pantalla de inicio
+
+ Regresa rápidamente a tus sitios favoritos en %1$s. Selecciona \"Añadir a la pantalla de inicio\" desde el menú %1$s.
+
+
+ Haz de la privacidad un hábito
+
+ Establece %1$s como navegador predeterminado y experimenta los beneficios de la navegación privada al abrir páginas web desde otras aplicaciones.
+
+ Ok, ¡ya caché!
+ Saltar
+ Siguiente
+
+
+ -
+
+
+ Añadir
+
+
+ SÍ
+
+
+ Cancelar
+
+
+ NO
+
+
+ El acceso directo se abrirá con la protección de seguimiento mejorada desactivada
+
+
+ Sesión de navegación privada
+
+
+ Las notificaciones te permiten eliminar tu sesión de %1$s con un solo toque. No necesitas abrir la aplicación o ver lo que está corriendo en el navegador.
+
+
+ Eliminar historial de navegación
+
+
+ Bajar Firefox
+
+
+
+
+
+
+
+
+ Licencia Pública de Mozilla y otras licencias de código abierto.]]>
+
+
+ aquí.]]>
+
+
+ licencias gratuitas y de código abierto.]]>
+
+
+ GPLv3 (GNU General Public License v3), y se encuentra disponible aquí .]]>
+
+
+ Nombre de usuario
+ Contraseña
+ Limpiar
+
+
+
+ Conexión segura
+ Conexión insegura
+
+ Verificado por: %1$s
+
+
+ Seguridad del sitio
+ La URL ya existe
+
+
+ Buscar en la página
+
+
+ Buscar en la página
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Buscar el siguiente resultado
+
+ Buscar el resultado anterior
+
+ Cerrar búsqueda en la página
+
+
+
+
+ Solicitar sitio de escritorio
+
+
+ Sitio de escritorio
+
+
+ URL copiada
+
+
+ Herramientas de desarrollador
+
+
+ Abrir enlaces en aplicaciones
+
+
+ Avanzado
+
+
+ Permisos del sitio
+
+
+ Reducción de anuncios de cookies
+
+
+ Activada
+
+
+ Desactivada
+
+
+ Reducción de anuncios de cookies
+
+
+ Mira menos anuncios rechazando automáticamente las solicitudes de cookies, cuando sea posible.
+
+ -->
+ Reducción de anuncios de cookies
+
+
+ ACTIVADA para este sitio
+
+
+ Sitio actualmente no soportado
+
+
+ DESACTIVADA para este sitio
+
+
+ Reducción de anuncios de cookies
+
+
+ DESACTIVADA para este sitio
+
+
+ ACTIVADA para este sitio
+
+
+ ¿Activar la reducción de anuncios de cookies para %1$s?
+
+
+ ¿Desactivar la reducción de anuncios de cookies para %1$s?
+
+
+ %1$s borrará las cookies de este sitio y recargará la página. Borrar todas las cookies puede cerrar tu sesión o vaciar los carritos de compras.
+
+
+ %1$s puede intentar rechazar automáticamente las solicitudes de cookies.
+
+
+ Actualmente, este sitio no es compatible con la reducción de anuncios de cookies. ¿Te gustaría solicitar a nuestro equipo que revise este sitio web y sea compatible en el futuro?
+
+
+ Cancelar
+
+
+ Pedir que funcione
+
+
+ Solicitud para compatibilizar el sitio enviada.
+
+
+ Solicitud para compatibilizar el sitio enviada.
+
+
+
+ %1$s intenta rechazar las solicitudes de cookies para descartar los molestos anuncios de cookies.\n\nAdministra las preferencias de anuncios de cookies en %2$s.
+
+ ajustes
+
+
+ Autoreproducción
+
+
+ Para permitirlo:
+
+
+ 1. Ve a los ajustes de Android
+
+
+ Permisos]]>
+
+
+ Ir a ajustes
+
+
+ %1$s]]>
+
+
+ Cámara
+
+
+ Micrófono
+
+
+ Ubicación
+
+
+ Notificación
+
+
+ Contenido controlado por DRM
+
+
+ Pedir permiso
+
+
+ Bloqueado
+
+
+ Permitido
+
+
+ Bloqueado por Android
+
+
+ Permitir audio y video
+
+
+ Bloquear solo audio
+
+
+ Recomendado
+
+
+ Bloquear audio y video
+
+
+ Estudios
+
+
+ Firefox podría instalar y realizar experimentos de vez en cuando.
+
+
+ Aprender más
+
+
+ La aplicación se cerrará para aplicar cambios
+
+
+ Eliminar
+
+
+ Activos
+
+
+ Completado
+
+
+ Depuración remota vía USB/Wi-Fi
+
+
+ Desbloquear
+
+
+ Confirma usando tu huella dactilar
+
+
+ Puedes usar tu huella dactilar para continuar con tu sesión actual de la aplicación.
+
+
+ Abrir enlace en una nueva sesión
+
+
+ Ícono de huella digital
+
+
+ Huella digital no reconocida. Vuelve a intentarlo.
+
+
+ El dedo fue movido muy rápido. Vuelve a intentarlo.
+
+
+ ¿Mostrar sugerencias de búsqueda?
+
+
+ Para recibir sugerencias, %1$s necesita enviar lo que escribas en la barra de direcciones al motor de búsqueda.
+
+
+ No
+
+
+ Sí
+
+
+ Algunos motores de búsqueda no pueden mostrar sugerencias.
+
+
+ Ocultar
+
+
+
+
+ ¿El sitio se comporta de forma inesperada?\n Intenta desactivar la protección de seguimiento
+
+
+ Añadir a la pantalla de inicio]]>
+
+
+ Abre todos los enlaces en %1$s\n Configura %1$s como tu navegador predeterminado
+
+
+ Autocompleta las URLs para los sitios que más usas\n Manten presionada cualquier URL en la barra de direcciones
+
+
+ Abre un enlace en una nueva pestaña\n Mantén presionado cualquier enlace en una página
+
+
+ Desactivar los consejos en la pantalla de inicio
+
+
+ Nueva pestaña abierta
+
+
+ Cambiar
+
+
+ Pasando a modo de pantalla completa
+
+
+ Cambiar al enlace en la nueva pestaña inmediatamente
+
+
+ Bloquear sitios potencialmente peligrosos y engañosos
+
+ Bloquear sitios reportados por fraudes y ataques, sitios de malware y sitios de software no deseado.
+
+
+ Modo solo HTTPS
+
+
+ Intenta conectarse automáticamente a sitios utilizando el protocolo de cifrado HTTPS para mayor seguridad.
+
+
+ Excepciones
+
+ Has desactivado el bloqueo de contenido para estos sitios.
+
+ Eliminar
+
+ Eliminar todos los sitios
+
+
+ Bloquear cookies
+
+
+ ¿Te gustaría bloquear las cookies?
+
+
+ Pestaña caida
+
+ Lo sentimos. Estamos teniendo un problema con esta pestaña.
+
+ Como un navegador privado nunca guardaremos esta pestaña, por lo que tampoco podremos restaurarla.
+
+ Cerrar pestaña
+
+
+
+
+
+ Enviar reporte de fallos a Mozilla
+
+
+
+
+ %s rastreadores bloqueados desde la instalación
+
+ Contenido
+
+ Publicidad
+
+ Social
+
+ Analítica
+
+ Protección de seguimiento mejorada
+
+ Las protecciones están desactivadas para este sitio
+
+ Las protecciones están activadas para este sitio
+
+ Conexión segura
+
+ Conexión no segura
+
+ Rastreadores y scripts a bloquear
+
+
+ Retroceder
+
+
+
+ Eliminar
+
+
+ Renombrar
+
+ Renombrar
+
+
+ Nombre del acceso directo
+
+
+ Las imágenes guardadas y compartidas <b>no serán</b> eliminadas cuando borres el historial de %1$s
+
+
+
+ Tema
+
+ Claro
+
+ Oscuro
+
+ Establecido por el ahorrador de batería
+
+ Usar el tema del dispositivo
+
+
+
+ Este sitio no soporta HTTPS
+
+
+ Aprender más
+ Cambia esta configuración en Ajustes > Privacidad y seguridad > Seguridad.]]>
+
+
+ Conexión no segura
+
+
+
+Si ha conectado a este servidor con éxito en el pasado, el error podría ser temporal.]]>
+
+
+ Alguien podría estar intentando hacerse pasar por el sitio y continuar podría ser riesgoso.
+
+ %1$s no confía en %2$s porque el emisor de su certificado es desconocido, el certificado fue auto-firmado o el servidor no está enviando los certificados intermediarios correctos.
+ ]]>
+
+
+
+ Cerrar pestaña
+
+
+
+ ¡Lo agarramos! Evitamos que este sitio te espiara. Toca el escudo para ver lo que estamos bloqueando.
+
+
+ Cerrar elemento emergente
+
+
+
+ ¡Estás bajo protección!
+
+ Esta configuración predeterminada ofrece una protección fuerte. Pero es fácil ajustar la configuración para satisfacer tus necesidades específicas.
+
+ Ocultar
+
+
+ Toca aquí para eliminarlo todo — historial, cookies, absolutamente todo — y comienza de cero en una nueva pestaña.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Cerrar
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget de búsqueda
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ¡Historial de navegación limpiado! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Inicia tu sesión de navegación privada y bloquearemos rastreadores y otras amenazas mientras navegas.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Puedes continuar con tu navegación privada, pero recuerda que puedes llegar más rápido a ella la próxima vez que la necesites con el widget de %1$s en tu pantalla de inicio.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Agregar widget a la pantalla de inicio
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget agregado a la pantalla de inicio
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-es-rES/strings.xml b/mobile/android/focus-android/app/src/main/res/values-es-rES/strings.xml
new file mode 100644
index 0000000000..9e2aa7413a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-es-rES/strings.xml
@@ -0,0 +1,1117 @@
+
+
+
+
+
+
+
+
+ Cancelar
+
+ Aceptar
+
+ Guardar
+
+
+ Buscar o escribir dirección
+
+ Navegación privada automática.\nNavega. Borra. Repite.
+
+
+ Se ha eliminado tu historial de búsqueda.
+
+ Se borró el historial de navegación
+
+
+ Se ha borrado el historial de navegación de la pestaña.
+
+
+ Buscar %1$s
+
+
+ Compartir…
+
+
+ Informar de fallo en el sitio
+
+
+ Abrir en %1$s
+
+
+ Abrir en…
+
+
+ Añadir a pantalla de inicio
+
+
+ Añadir a accesos directos
+
+ Eliminar de los accesos directos
+
+
+ Ajustes
+ Acerca de
+ Ayuda
+ Tus derechos
+
+
+ Rastreadores bloqueados
+
+
+ Desactivar esta opción puede solucionar algunos problemas del sitio
+
+
+ Bloqueo de contenido
+
+ Desactivar para arreglar algunos sitios
+
+
+ Impulsado por %1$s
+
+
+ Compartir vía
+
+ ¿Eliminar historial de navegación?
+ Toca o elimina esta notificación para borrar de forma segura tu historial de navegación.
+
+
+ Toca o desliza esta notificación para borrar de forma segura tu historial de navegación.
+
+ Eliminar historial de navegación
+
+
+ Abrir
+
+
+ Borrar y abrir
+
+
+ Eliminar
+
+
+ Eliminar historial de navegación
+
+
+
+ Borrar y abrir
+
+
+ Borrar y abrir %1$s
+
+
+
+ Buscar en Focus
+
+ Buscar en Klar
+
+ Buscar en Focus Beta
+
+ Buscar en Focus Nightly
+
+
+ %1$s te da el control.
+Úsalo como navegador privado:
+
+ Busca y navega en la propia aplicación
+ Bloquea a los rastreadores (o cambia los ajustes para permitirlos)
+ Limpia para borrar cookies y los historiales de búsqueda y navegación
+
+
+%1$s es producido por Mozilla. Nuestra misión es promover un Internet saludable y abierto.
+Descubre más
]]>
+
+
+ Privacidad y seguridad
+
+
+ Opciones de rastreo, cookies y datos
+
+
+ Establecer como predeterminado, autocompletar
+
+
+
+
+ Acerca de %1$s, ayuda
+
+
+ Protección mejorada contra el rastreo
+
+
+ Contenido web
+
+
+ Cambiar entre aplicaciones
+
+
+ General
+
+
+ Navegador predeterminado, idioma
+
+
+ Recopilación de datos y uso
+
+ Búsqueda
+
+
+ Obtener sugerencias de búsqueda
+
+ %1$s enviará lo que escribas en la barra de direcciones a tu buscador
+
+
+ Predeterminado
+
+
+ Buscador
+
+
+ Activado
+
+
+ Desactivado
+
+
+ Autocompletar URL
+
+
+ Para los sitios frecuentes
+
+
+ Activar para que %s complete automáticamente más de 450 URLs populares en la barra de direcciones.
+
+
+ Para los sitios que añades
+
+
+ Activar para que %s complete automáticamente tus URLs favoritas.
+
+
+ Administrar sitios
+
+
+ Administrar sitios
+
+
+ + Agregar URL personalizada
+
+
+ Tu lista de autocompletado:
+
+
+ Añadir URL
+
+
+ Agregar URL personalizada
+
+
+ Agregar URL personalizada
+
+
+ Añadir el enlace para completar automáticamente
+
+
+ Cookies y datos del sitio
+
+
+ Opciones de datos
+
+
+ Eliminar URLs personalizadas
+
+
+ Descubrir más
+
+
+ Agregar y gestionar autocompletado personalizado de URLs.
+
+
+ Agregar URL
+
+
+ Pegar o escribir URL
+
+
+ Ejemplo: mozilla.org
+
+
+ Ejemplo: ejemplo.com
+
+
+ Se agregó una nueva URL personalizada.
+
+
+ Eliminar
+
+
+ Eliminar
+
+
+ Volver a comprobar la URL que has escrito.
+
+ Idioma
+
+ Predeterminado del sistema
+
+ Privacidad
+ Bloquear rastreadores de publicidad
+ Algunos anuncios, aunque no accedas a ellos, rastrean tus visitas a páginas web
+ Bloquear rastreadores analíticos
+ Se utilizan para recopilar, analizar y medir tus actividades, como cuándo seleccionas algo o te deslizas por la página
+ Bloquear rastreadores sociales
+ Incorporados en las páginas para rastrear tus visitas y mostrar funcionalidad, como botones para compartir
+ Bloquear rastreadores de otros contenidos
+ Si lo activas, puede que algunas páginas no funcionen correctamente
+ Bloquear cookies
+
+
+ No, gracias
+ Bloquear solo cookies de rastreo de terceros
+ Bloquear sólo cookies de terceros
+ Bloquear cookies entre sitios
+ Sí, por favor
+
+
+ Usar la huella digital para desbloquear la aplicación
+
+
+ Desbloquea usando la huella digital si añadiste accesos directos o cuando un sitio web ya está abierto en %s.
+
+
+ Invisible
+
+ Ocultar páginas web al cambiar entre aplicaciones y bloquear las capturas de pantalla.
+
+ Seguridad
+
+ Rendimiento
+ Bloquear fuentes web
+
+ Puede hacer que falten iconos o imágenes
+
+ Bloquear JavaScript
+
+ Las páginas pueden cargar más rápido, pero también pueden comportarse de forma inesperada
+
+
+ Convertir %1$s en tu navegador predeterminado
+
+ Mozilla
+ Enviar datos de consumo
+
+
+ Descubrir más
+
+
+ Mozilla se esfuerza por recopilar solo lo necesario para proporcionar y mejorar %1$s para todos.
+
+
+ Aviso de privacidad
+
+
+ Información de licencia
+
+
+ Bibliotecas que usamos
+
+
+ %s | Bibliotecas OSS
+
+
+ Acerca de %1$s
+
+
+ Motores de búsqueda instalados
+
+
+ Elige un motor de búsqueda
+
+
+ Restaurar motores de búsqueda predeterminados
+
+
+ + Añadir otro motor de búsqueda
+ Eliminar motores de búsqueda
+ Eliminar
+
+ Añade otro motor de búsqueda
+
+ Selecciona tu motor preferido:
+
+
+ Añadir motor de búsqueda
+
+ Nombre del motor de búsqueda
+ Cadena de búsqueda que se usará
+ Guardar
+
+
+ Ejemplo: example.com/search/?q=%s
+
+ Nuevo motor de búsqueda añadido.
+
+ Introducir nombre del motor de búsqueda
+ Un motor de búsqueda instalado ya está usando ese nombre.
+
+ Introducir cadena de búsqueda
+
+ Comprueba que la cadena de búsqueda coincide con el formato del ejemplo
+
+
+ Limpiar registro
+
+
+ Ignorar
+
+
+ Eliminar historial de navegación
+
+
+ Pestañas abiertas: %1$s
+
+
+ Conexión segura
+
+
+ Cargando
+
+
+ Página cargada
+
+
+ Más opciones
+
+
+ Botón de más opciones
+
+
+ Ir hacia delante
+
+
+ Recargar página
+
+
+ Ir a la página anterior
+
+
+ Parar la carga de esta página
+
+
+ Volver a la aplicación anterior
+
+
+ Número de rastreadores bloqueados
+
+
+ Bloquear rastreadores
+
+ Tus derechos
+
+ Abrir enlace con otra aplicación
+
+ Puedes salir de %1$s para abrir este enlace en %2$s.
+
+ Busca una aplicación que pueda abrir este enlace
+
+ Ninguna de las aplicaciones de tu dispositivo puede abrir este enlace. Puedes salir de %1$s para buscar en %2$s una aplicación compatible.
+
+ ¿Salir de la navegación privada?
+
+
+ %1$s completado
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+
+ ¡Añadido a los accesos directos!
+
+ Servidor no encontrado
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cerrar
+
+
+
+ Te damos la bienvenida a %1$s
+
+
+ Rápido. Privado. Sin distracciones.
+
+
+ Comenzar
+
+
+
+ %1$s no es como otros navegadores
+
+
+ Borramos tu historial cuando cierras la aplicación para ofrecerte mayor privacidad.
+
+
+
+ Establece %1$s como predeterminado para proteger tus datos con cada enlace que abras.
+
+
+ Establecer como navegador predeterminado
+
+
+ Saltar
+
+
+
+ Potencia tu privacidad
+
+ Lleva la navegación privada al siguiente nivel. Bloquea anuncios y otros contenidos que puedan rastrearte a través de los sitios y ralentizar la carga de las páginas.
+
+
+ Tu búsqueda, a tu manera
+
+ ¿Buscas algo diferente? Elige otro motor de búsqueda predeterminado en Ajustes.
+
+
+ Añade accesos directos a tu pantalla de inicio
+
+ Vuelve a visitar tus sitios favoritos en %1$s de forma instantánea. En el menú %1$s, selecciona \"Agregar a la pantalla de inicio\".
+
+
+ Haz de la privacidad un hábito
+
+ Establece %1$s como navegador predeterminado y experimenta los beneficios de la navegación privada al abrir páginas web desde otras aplicaciones.
+
+ ¡Entendido!
+ Saltar
+ Siguiente
+
+
+ -
+
+
+ Añadir
+
+ SÍ
+
+
+ Cancelar
+
+ NO
+
+
+ El acceso directo se abrirá con la protección mejorada contra el rastreo
+
+
+ Sesión de navegación privada
+
+
+ Las notificaciones te permiten borrar la sesión de %1$s con un toque. No necesitas abrir la aplicación ni ver qué se está ejecutando en el navegador.
+
+
+ Eliminar historial de navegación
+
+
+ Descargar Firefox
+
+
+
+
+
+
+
+
+ Licencia Pública de Mozilla y otras licencias de código abierto.]]>
+
+
+ aquí.]]>
+
+
+ licencias libres y de código abierto.]]>
+
+
+ GNU General Public License v3, y se encuentra disponible aquí .]]>
+
+
+ Nombre de usuario
+ Contraseña
+ Limpiar
+
+
+
+ Conexión segura
+ Conexión no segura
+
+ Verificado por: %1$s
+
+
+ Seguridad del sitio
+ La URL ya existe
+
+
+ Buscar en la página
+
+
+ Buscar en la página
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Próximo resultado
+
+ Resultado anterior
+
+ Cerrar la búsqueda
+
+
+
+
+ Solicitar sitio de escritorio
+
+
+ Sitio de escritorio
+
+
+ Se copió la URL
+
+
+ Herramientas de desarrollo
+
+
+ Abrir enlaces en aplicaciones
+
+
+ Avanzado
+
+
+ Permisos del sitio
+
+
+ Reducción de avisos de cookies
+
+
+ Activada
+
+
+ Desactivada
+
+
+ Reducción de avisos de cookies
+
+
+ Verás menos avisos rechazando automáticamente las solicitudes de cookies cuando sea posible.
+
+ -->
+ Reducción de avisos de cookies
+
+
+ ACTIVADA para este sitio
+
+
+ Sitio actualmente no compatible
+
+
+ DESACTIVADA para este sitio
+
+
+ Reducción de avisos de cookies
+
+
+ DESACTIVADA para este sitio
+
+
+ ACTIVADA para este sitio
+
+
+ ¿Activar la reducción de aviso de cookies para %1$s?
+
+
+ ¿Desactivar la reducción de aviso de cookies para %1$s?
+
+
+ %1$s borrará las cookies de este sitio y recargará la página. Borrar todas las cookies puede cerrar tu sesión o vaciar los carritos de compras.
+
+
+ %1$s puede intentar rechazar automáticamente las solicitudes de cookies.
+
+
+ Este sitio actualmente no es compatible con la reducción de avisos de cookies. ¿Quieres pedirle a nuestro equipo que revise este sitio web y añada soporte en el futuro?
+
+
+ Cancelar
+
+
+ Pedir soporte
+
+
+ La solicitud de soporte ha sido enviada.
+
+
+ La solicitud de soporte ha sido enviada.
+
+
+
+ %1$s intenta rechazar las solicitudes de cookies para descartar los molestos avisos de cookies.\n\nAdministra las preferencias de avisos de cookies en los ajustes en %2$s.
+
+
+ ajustes
+
+
+ Reproducción automática
+
+
+ Para permitirlo:
+
+
+ 1. Ir a Ajustes de Android
+
+
+ Permisos]]>
+
+
+ Ir a Ajustes
+
+
+ %1$s]]>
+
+
+ Cámara
+
+
+ Micrófono
+
+
+ Ubicación
+
+
+ Notificación
+
+
+ Contenido controlado por DRM
+
+
+ Pedir permiso
+
+
+ Bloqueado
+
+
+ Permitido
+
+
+ Bloqueado por Android
+
+
+ Permitir audio y vídeo
+
+
+ Bloquear solo audio
+
+
+ Recomendado
+
+
+ Bloquear audio y vídeo
+
+
+ Estudios
+
+
+ Firefox puede instalar y ejecutar estudios de vez en cuando.
+
+
+ Saber más
+
+
+ La aplicación se cerrará para aplicar los cambios
+
+
+ Eliminar
+
+
+ Activo
+
+
+ Completados
+
+
+ Depuración remota a través de USB/Wi-Fi
+
+
+ Desbloquear
+
+
+ Confirma usando tu huella dactilar
+
+
+ Puedes usar tu huella dactilar para continuar con tu sesión actual de la aplicación.
+
+
+ Abrir el enlace en una nueva sesión
+
+
+ Icono de huella digital
+
+
+ Huella digital no reconocida. Vuelve a intentarlo.
+
+
+ El dedo se movió demasiado rápido. Vuelve a intentarlo.
+
+
+ ¿Mostrar sugerencias de búsqueda?
+
+
+ Para recibir sugerencias, %1$s necesita enviar lo que escribas en la barra de direcciones al buscador.
+
+
+ No
+
+
+ Si
+
+
+ Algunos motores de búsqueda no pueden mostrar sugerencias.
+
+
+ Ignorar
+
+
+
+
+ ¿El sitio se comporta de forma inesperada?\n Intenta desactivar la protección contra el rastreo
+
+
+ Añadir a la pantalla de inicio]]>
+
+
+ Abrir todos los enlaces en %1$s\n Establecer %1$s como navegador predeterminado
+
+
+ Autocompletar las URLs para los sitios que más usas\n Mantén presionada cualquier URL en la barra de direcciones
+
+
+ Abrir un enlace en una nueva pestaña\n Mantén presionado cualquier enlace de una página
+
+
+ Desactivar los consejos en la pantalla de inicio
+
+
+ Nueva pestaña abierta
+
+
+ Cambiar
+
+
+ Accediendo a pantalla completa
+
+
+ Cambie a un enlace en una nueva pestaña inmediatamente
+
+
+ Bloquear sitios potencialmente peligrosos y engañosos
+
+ Bloqueo de sitios informados como fraudulentos y de ataques, de malware y de software no deseado.
+
+
+ Modo solo HTTPS
+
+
+ Intenta conectarse automáticamente a sitios utilizando el protocolo de cifrado HTTPS para mayor seguridad.
+
+
+ Excepciones
+
+ Ha desactivado el bloqueo de contenido para estos sitios.
+
+ Eliminar
+
+ Eliminar todos los sitios web
+
+
+ Bloquear cookies
+
+
+ ¿Te gustaría bloquear las cookies?
+
+
+ La pestaña falló
+
+ Lo sentimos. Tenemos problemas con esta pestaña.
+
+ Como navegador privado, nunca guardamos y no podemos restaurar esta pestaña.
+
+ Cerrar pestaña
+
+
+
+
+
+ Enviar informe de fallo a Mozilla
+
+
+
+
+ Rastreadores bloqueados desde %s
+
+ Contenido
+
+ Publicidad
+
+ Social
+
+ Analítica
+
+ Protección mejorada contra el rastreo
+
+ Las protecciones están desactivadas para este sitio
+
+ Las protecciones están activadas para este sitio
+
+ Conexión segura
+
+ Conexión no segura
+
+ Rastreadores y scripts a bloquear
+
+
+ Retroceder
+
+
+
+ Eliminar
+
+
+ Renombrar
+
+ Renombrar
+
+
+ Nombre de acceso directo
+
+
+ Las imágenes guardadas y compartidas <b>no serán</b> eliminadas cuando borres el historial de %1$s
+
+
+
+ Tema
+
+ Claro
+
+ Oscuro
+
+ Establecido por el ahorro de batería
+
+ Usar el tema del dispositivo
+
+
+
+ Este sitio no admite HTTPS
+
+
+ Saber más
+ Cambia esta configuración en Ajustes > Privacidad y seguridad > Seguridad.]]>
+
+
+ Conexión no segura
+
+
+
+ Si en el pasado te has conectado correctamente a este servidor, el error podría ser temporal.
+ ]]>
+
+
+ Alguien podría estar intentando hacerse pasar por el sitio y continuar podría ser peligroso.
+
+ %1$s no confía en %2$s porque el emisor del certificado es desconocido, el certificado está autofirmado o el servidor no envía los certificados intermedios correctos.
+ ]]>
+
+
+
+ Cerrar pestaña
+
+
+
+ ¡Excelente! Hemos evitado que este sito web te espiara. Toca el escudo para ver lo que estamos bloqueando.
+
+
+ Cerrar elemento emergente
+
+
+
+ ¡Estás protegido!
+
+ Esta configuración predeterminada ofrece una fuerte protección. Pero es fácil ajustar la configuración para satisfacer tus necesidades específicas.
+
+ Descartar
+
+
+ Toca aquí para eliminarlo todo — historial, cookies, absolutamente todo — y comenzar desde cero en una nueva pestaña.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Cerrar
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Buscar widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ¡Historial de navegación borrado! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Inicia tu sesión de navegación privada y bloquearemos rastreadores y otras amenazas mientras navegas.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Te dejaremos con tu navegación privada, pero comienza más rápido la próxima vez con el widget de %1$s en tu pantalla de inicio.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Añadir widget a la pantalla de inicio
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget añadido a la pantalla de inicio
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-es-rMX/strings.xml b/mobile/android/focus-android/app/src/main/res/values-es-rMX/strings.xml
new file mode 100644
index 0000000000..f9de0a686c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-es-rMX/strings.xml
@@ -0,0 +1,1111 @@
+
+
+
+
+
+
+
+
+ Cancelar
+
+ OK
+
+ Guardar
+
+
+ Buscar o ingresar dirección
+
+ Navegación privada automática.\nNavega. Limpia. Repite.
+
+
+ Se ha eliminado tu historial de búsqueda.
+ Se borró el historial de navegación
+
+
+ Se ha borrado el historial de navegación de la pestaña.
+
+
+ Buscar %1$s
+
+
+ Compartir…
+
+
+ Reportar problema con el sitio
+
+
+ Abrir en %1$s
+
+
+ Abrir en…
+
+
+ Agregar a la pantalla de inicio
+
+
+ Agregar a accesos directos
+
+ Eliminar de los accesos directos
+
+ Configuración
+ Acerca de
+ Ayuda
+ Tus derechos
+
+
+ Rastreadores bloqueados
+
+
+ Desactivar esta opción puede solucionar algunos problemas del sitio
+
+
+ Bloqueo de contenido
+
+ Desactivar para arreglar algunos sitios
+
+
+ Impulsado por %1$s
+
+
+ Compartir vía
+
+
+ Eliminar historial de navegación
+
+
+ Abrir
+
+
+ Borrar y abrir
+
+
+ Borrar
+
+
+ Eliminar historial de navegación
+
+
+
+ Borrar y abrir
+
+
+ Borrar y abrir %1$s
+
+
+
+ Buscar en Focus
+
+ Buscar en Klar
+
+ Buscar en Focus Beta
+
+ Buscar en Focus Nightly
+
+
+ %1$s te da el control.
+Úsalo como navegador privado:
+
+ Busca y navega en la propia aplicación
+ Bloquea a los rastreadores (o cambia los ajustes para permitirlos)
+ Limpia para borrar cookies y los historiales de búsqueda y navegación
+
+
+%1$s es producido por Mozilla. Nuestra misión es promover un Internet saludable y abierto.
+Descubre más
]]>
+
+
+ Privacidad y seguridad
+
+
+ Opciones de rastreo, cookies y datos
+
+
+ Establecer como predeterminado, autocompletar
+
+
+
+
+ Acerca de %1$s, ayuda
+
+
+ Protección mejorada contra el rastreo
+
+
+ Contenido web
+
+
+ Cambiando aplicaciones
+
+
+ General
+
+
+ Navegador predeterminado, idioma
+
+
+ Recolección de datos y uso
+
+ Buscar
+
+
+ Obtener sugerencias de búsqueda
+
+ %1$s enviará lo que escribas en la barra de direcciones a tu motor de búsqueda
+
+
+ Predeterminado
+
+
+ Motor de búsqueda
+
+
+ Activar
+
+
+ Desactivar
+
+
+ Autocompletar URL
+
+
+ Para sitios favoritos
+
+
+ Activar para que %s autocomplete más de 450 URLs populares en la barra de direcciones.
+
+
+ Para los sitios que agregues
+
+
+ Activar para que %s autocomplete tus URLs favoritas.
+
+
+ Administrar sitios
+
+
+ Administrar sitios
+
+
+ + Agregar URL personalizada
+
+
+ Tu lista de autocompletado:
+
+
+ Agregar URL
+
+
+ Agregar URL personalizada
+
+
+ Agregar URL personalizada
+
+
+ Agregar enlace para completar automáticamente
+
+
+ Cookies y datos del sitio
+
+
+ Opciones de datos
+
+
+ Eliminar las URL personalizadas
+
+
+ Saber más
+
+
+ Agregar y gestionar el autocompletado de URL personalizadas.
+
+
+ URL a agregar
+
+
+ Pegar o introducir URL
+
+
+ Ejemplo: mozilla.org
+
+
+ Ejemplo: example.com
+
+
+ Nueva URL personalizada agregada.
+
+
+ Eliminar
+
+
+ Eliminar
+
+
+ Vuelve a verificar la URL que introdujiste.
+
+ Idioma
+
+ Valor predeterminado del sistema
+
+ Privacidad
+ Bloquear rastreadores de publicidad
+ Algunos anuncios rastrean tus visitas a sitios, aunque no hagas clic en ellos
+ Bloquear rastreadores analíticos
+ Utilizado para recolectar, analizar y medir actividades como pulsar y deslizar
+ Bloquear rastreadores sociales
+ Incrustado en sitios para rastrear tus visitas y mostrar funcionalidades como botones de compartir
+ Bloquear rastreadores de otros contenidos
+ Activar puede causar que algunas páginas se comporten inesperadamente
+ Bloquear cookies
+
+
+ No, gracias
+ Bloquear solo cookies de rastreo de terceros
+ Bloquear solo cookies de terceros
+ Bloquear cookies entre sitios
+ Sí, por favor
+
+
+ Usar huella digital para desbloquear aplicación
+
+
+ Desbloquea usando la huella digital si agregaste accesos directos o cuando un sitio web ya está abierto en %s.
+
+
+ Invisible
+
+ Ocultar páginas al intercambiar aplicaciones y bloquear la toma de capturas de pantalla.
+
+ Seguridad
+
+ Rendimiento
+ Bloquear fuentes web
+
+ Puede resultar en pérdida de íconos o imágenes
+
+ Bloquear JavaScript
+
+ Las páginas pueden cargar más rápido, pero pueden comportarse de manera inesperada
+
+
+ Convierte %1$s en tu navegador predeterminado
+
+ Mozilla
+ Enviar datos de consumo
+
+
+ Saber más
+
+
+ Mozilla lucha por recolectar solo lo necesario para brindar y mejorar %1$s para todos.
+
+
+ Aviso de privacidad
+
+
+ Información de licencia
+
+
+ Bibliotecas que usamos
+
+
+ %s | Bibliotecas OSS
+
+
+ Acerca de %1$s
+
+
+ Motores de búsqueda instalados
+
+
+ Elegir motor de búsqueda
+
+
+ Restaurar motores predeterminados de búsqueda
+
+
+ + Agregar otro motor de búsqueda
+ Eliminar motores de búsqueda
+ Eliminar
+
+ Agregar otro motor de búsqueda
+
+ Selecciona tu motor de búsqueda preferido:
+
+
+ Agregar motor de búsqueda
+
+ Nombre del motor de búsqueda
+ Buscar cadena para usar
+ Guardar
+
+
+ Ejemplo: example.com/search/?q=%s
+
+ Nuevo motor de búsqueda añadido.
+
+ Introducir el nombre del motor de navegación
+ Un motor de búsqueda instalado ya está usando ese nombre.
+
+ Introducir la cadena de búsqueda
+
+ Verificar que la cadena de búsqueda corresponda al formato del ejemplo
+
+
+ Limpiar registro
+
+
+ Ignorar
+
+
+ Eliminar historial de navegación
+
+
+ Pestañas abiertas: %1$s
+
+
+ Conexión segura
+
+
+ Cargando
+
+
+ Sitio web cargado
+
+
+ Más opciones
+
+
+ Botón de más opciones
+
+
+ Ir hacia delante
+
+
+ Recargar sitio web
+
+
+ Regresar a la navegación
+
+
+ Detener la carga del sitio web
+
+
+ Regresar a la aplicación anterior
+
+
+ Número de rastreadores bloqueados
+
+
+ Bloquear rastreadores
+
+ Tus derechos
+
+ Abrir enlace en otra aplicación
+
+ Puedes salir de %1$s para abrir este enlace en %2$s.
+
+ Buscar una aplicación que pueda abrir el enlace
+
+ Ninguna de las aplicaciones en tu dispositivo puede abrir este enlace. Puedes salir de %1$s para buscar una aplicación compatible en %2$s.
+
+ ¿Salir de la navegación privada?
+
+
+ %1$s finalizado
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+ No se encontró el servidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cerrar
+
+
+
+ Te damos la bienvenida a %1$s
+
+
+ Rápido. Privado. Sin distracciones.
+
+
+ Comenzar
+
+
+
+ %1$s no es como otros navegadores
+
+
+ Borramos tu historial cuando cierras la aplicación para mayor privacidad.
+
+
+
+ Establece %1$s como predeterminado para proteger tus datos con cada enlace que abras.
+
+
+ Establecer como navegador predeterminado
+
+
+
+ Saltar
+
+
+
+ Potencia tu privacidad
+
+ Lleva la navegación privada a otro nivel. Bloquea anuncios publicitarios y otro tipo de contenido que puede rastrearte a través de los sitios y ralentizar el tiempo de descarga de las páginas.
+
+
+ Tu búsqueda, a tu manera
+
+ ¿Buscas algo diferente? Elige otro motor de búsqueda predeterminado en la configuración.
+
+
+ Agrega atajos a la pantalla principal
+
+ Regresa rápidamente a tus sitios favoritos en %1$s. Selecciona \"agregar a la página de inicio\" desde el menú %1$s.
+
+
+ Acostúmbrate a la privacidad
+
+ Establece %1$s como navegador predeterminado y experimenta los beneficios de la navegación privada al abrir páginas web desde otras aplicaciones.
+
+ ¡Entendido!
+ Ignorar
+ Siguiente
+
+
+ -
+
+
+ Agregar
+
+ SÍ
+
+
+ Cancelar
+
+ NO
+
+
+ El acceso directo se abrirá con la protección mejorada contra el rastreo deshabilitada
+
+
+ Sesión de navegación privada
+
+
+ Las notificaciones permiten borrar la sesión de %1$s con un toque. No necesitas abrir la aplicación o ver lo que está funcionando en el navegador.
+
+
+ Borrar historial de navegación
+
+
+ Descarga Firefox
+
+
+
+
+
+
+
+
+ Licencia Pública de Mozilla y otras licencias de código abierto.]]>
+
+
+ aquí.]]>
+
+
+ licencias libres y de código abierto.]]>
+
+
+ GNU General Public License v3, y se encuentra disponible aquí .]]>
+
+
+ Nombre de usuario
+ Contraseña
+ Limpiar
+
+
+
+ Conexión segura
+ Conexión insegura
+
+ Verificado por: %1$s
+
+
+ Seguridad del sitio
+ La URL ya existe
+
+
+ Buscar en la página
+
+
+ Buscar en la página
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Buscar resultado siguiente
+
+ Buscar resultado anterior
+
+ Cerrar la búsqueda
+
+
+
+
+ Pedir versión de escritorio
+
+
+ Sitio de escritorio
+
+
+ URL copiada
+
+
+ Herramientas de desarrollador
+
+
+ Abrir enlaces en aplicaciones
+
+
+ Avanzadas
+
+
+ Permisos del sitio
+
+
+ Reducción de banner de cookies
+
+
+ Activar
+
+
+ Desactivar
+
+
+ Reducción de banner de cookies
+
+
+ Mira menos banners automáticamente rechazando las solicitudes de cookies, cuando sea posible.
+
+ -->
+ Reducción de banner de cookies
+
+
+ ACTIVADO para este sitio
+
+
+ Sitio actualmente no compatible
+
+
+ DESACTIVADO para este sitio
+
+
+ Reducción de banner de cookies
+
+
+ DESACTIVADO para este sitio
+
+
+ ACTIVADO para este sitio
+
+
+ ¿Activar la reducción de banner de cookies para %1$s?
+
+
+ ¿Desactivar la reducción de banner de cookies para %1$s?
+
+
+ %1$s borrará las cookies de este sitio y actualizará la página. Borrar todas las cookies puede cerrar tu sesión o vaciar los carritos de compras.
+
+
+ %1$s puede intentar rechazar automáticamente las solicitudes de cookies.
+
+
+ Actualmente, este sitio no es compatible con la reducción de banner de cookies. ¿Te gustaría solicitar a nuestro equipo que revise este sitio web y agregue soporte en el futuro?
+
+
+ Cancelar
+
+
+ Solicitar soporte
+
+
+ La solicitud de soporte ha sido enviada.
+
+
+ La solicitud de soporte ha sido enviada.
+
+
+
+ %1$s intentara rechazar las solicitudes de cookies para descartar los molestos banners de cookies.\n
+\nAdministrar preferencias de banners de cookies en %2$s.
+
+
+ ajustes
+
+
+ Reproducción automática
+
+
+ Para permitirlo:
+
+
+ 1. Ir a los ajustes de Android
+
+
+ Permisos]]>
+
+
+ Ir a los ajustes
+
+
+ %1$s a Encendido]]>
+
+
+ Cámara
+
+
+ Micrófono
+
+
+ Ubicación
+
+
+ Notificación
+
+
+ Contenido controlado por DRM
+
+
+ Pedir permiso
+
+
+ Bloqueado
+
+
+ Permitido
+
+
+ Bloqueado por Android
+
+
+ Permitir audio y video
+
+
+ Bloquear solo audio
+
+
+ Recomendado
+
+
+ Bloquear audio y video
+
+
+ Estudios
+
+
+ Firefox puede instalar y ejecutar estudios de vez en cuando.
+
+
+ Saber más
+
+
+ La aplicación se cerrará para aplicar los cambios
+
+
+ Eliminar
+
+
+ Activo
+
+
+ Completados
+
+
+ Depuración remota vía USB/Wi-Fi
+
+
+ Desbloquear
+
+
+ Confirma usando tu huella digital
+
+
+ Puedes usar tu huella digital para continuar con tu sesión actual de la aplicación.
+
+
+ Abrir el enlace en una nueva sesión
+
+
+ Ícono de huella digital
+
+
+ Huella digital no reconocida. Intenta de nuevo.
+
+
+ El dedo se movió muy rápido. Intenta de nuevo.
+
+
+ ¿Mostrar sugerencias de búsqueda?
+
+
+ Para recibir sugerencias, %1$s necesita enviar lo que escribas en la barra de direcciones al buscador.
+
+
+ No
+
+
+ Sí
+
+
+ Algunos motores de búsqueda no pueden mostrar sugerencias.
+
+
+ Descartar
+
+
+
+
+ ¿El sitio se comporta de manera inesperada?\n Prueba desactivar la Protección de Rastreo
+
+
+ Agregar a la pantalla de inicio]]>
+
+
+ Abrir cada enlace en %1$s\n Establecer %1$s como navegador predeterminado
+
+
+ Autocompletar URLs para los sitios que más usas\n Mantén presionada cualquier URL en la barra de direcciones
+
+
+ Abrir enlace en una nueva pestaña\n Mantén presionado cualquier enlace en una página
+
+
+ Desactivar los consejos en la pantalla de inicio
+
+
+ Nueva pestaña abierta
+
+
+ Cambiar
+
+
+ Accediendo a pantalla completa
+
+
+ Cambiar a un enlace en una nueva pestaña inmediatamente
+
+
+ Bloquear sitios potencialmente peligrosos y engañosos
+
+ Bloquear sitios reportados por fraudes y ataques, sitios de malware y sitios de software no deseado.
+
+
+ Modo solo HTTPS
+
+
+ Intenta conectarse automáticamente a sitios utilizando el protocolo de encriptación HTTPS para mayor seguridad.
+
+
+ Excepciones
+
+ Has deshabilitado el bloqueo de contenido para estos sitios.
+
+ Eliminar
+
+ Eliminar todos los sitios
+
+
+ Bloquear cookies
+
+
+ ¿Te gustaría bloquear las cookies?
+
+
+ La pestaña ha fallado
+
+ Lo sentimos. Estamos teniendo un problema con esta pestaña.
+
+ Como navegador privado, no hemos guardado ni podemos restaurar esta pestaña.
+
+ Cerrar pestaña
+
+
+
+
+
+ Enviar reporte de fallos a Mozilla
+
+
+
+
+ Rastreadores bloqueados desde %s
+
+ Contenido
+
+ Publicidad
+
+ Social
+
+ Analítica
+
+ Protección mejorada contra el rastreo
+
+ Las protecciones están desactivadas para este sitio
+
+ Las protecciones están activadas para este sitio
+
+ La conexión es segura
+
+ La conexión no es segura
+
+ Rastreadores y scripts a bloquear
+
+
+ Retroceder
+
+
+
+ Eliminar
+
+
+ Renombrar
+
+ Renombrar
+
+
+ Nombre del acceso directo
+
+
+ Las imágenes guardadas y compartidas <b>no serán</b> eliminadas cuando borres el historial de %1$s
+
+
+
+ Tema
+
+ Claro
+
+ Oscuro
+
+ Establecido por el ahorrador de batería
+
+ Usar el tema del dispositivo
+
+
+
+ Este sitio no es compatible con HTTPS
+
+
+ Saber más
+ Cambia esta configuración en Ajustes > Privacidad & Seguridad > Seguridad.]]>
+
+
+ Conexión no segura
+
+
+
+ Si en el pasado te has conectado correctamente a este servidor, el error podría ser temporal.
+ ]]>
+
+
+ Alguien podría estar intentando hacerse pasar por el sitio y continuar podría ser peligroso.
+
+ %1$s no confía en %2$s porque el emisor del certificado es desconocido, el certificado está autofirmado o el servidor no envía los certificados intermedios correctos.
+ ]]>
+
+
+
+ Cerrar pestaña
+
+
+
+ ¡Excelente! Evitamos que esta página web te espíe. Presiona el escudo para ver lo que estamos bloqueando.
+
+
+ Cerrar elemento emergente
+
+
+
+ ¡Estás protegido!
+
+ Esta configuración predeterminada ofrece una fuerte protección. Pero es fácil ajustar la configuración para satisfacer tus necesidades específicas.
+
+ Descartar
+
+
+ Presiona aquí para eliminar todo — historial, cookies, y todo lo demás — e inicia en una nueva pestaña.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Cerrar
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget de búsqueda
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ¡Historial de navegación borrado! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Inicia tu sesión de navegación privada y bloquearemos rastreadores y otras amenazas mientras navegas.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Te dejaremos con tu navegación privada, pero comienza más rápido la próxima vez con el widget %1$s en tu pantalla de inicio.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Agregar widget a la pantalla de inicio
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget agregado a la pantalla de inicio
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-et/strings.xml b/mobile/android/focus-android/app/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000000..da74a70f6b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-et/strings.xml
@@ -0,0 +1,881 @@
+
+
+
+
+
+
+
+
+ Loobu
+
+ Olgu
+
+ Salvesta
+
+
+ Otsi või sisesta aadress
+
+ Automaatne privaatne lehitsemine.\nLehitse. Kustuta. Korda.
+
+
+ Lehitsemisajalugu on kustutatud.
+ Lehitsemisajalugu on kustutatud
+
+
+
+ Kaardi lehitsemisajalugu on kustutatud.
+
+
+ Otsi fraasi %1$s
+
+
+ Jaga…
+
+
+ Anna teada saidil olevast veast
+
+
+ Ava brauseris %1$s
+
+
+ Ava brauseriga…
+
+
+ Lisa avaekraanile
+
+
+ Lisa otseteedesse
+
+ Eemalda otseteede hulgast
+
+ Sätted
+ Teave
+ Abi
+ Sinu õigused
+
+
+ Blokitud jälitajaid
+
+
+ Selle väljalülitamine võib lahendada mõned saidiprobleemid
+
+
+ Sisu blokkimine
+
+ Mõne saidi parandamiseks lülita välja
+
+
+
+ Välja antud %1$s poolt
+
+
+ Jagamine kasutades
+
+
+ Kustuta lehitsemise ajalugu
+
+
+ Ava
+
+
+ Kustuta ja ava
+
+
+ Kustuta
+
+
+ Kustuta lehitsemise ajalugu
+
+
+
+ Kustuta ja ava
+
+
+ Kustuta ja ava %1$s
+
+
+
+ %1$s annab kontrolli sulle.
+Kasuta seda kui privaatset brauserit:
+
+ Otsi ja lehitse otse äpis
+ Bloki jälitajaid (või muuda sätteid, et mõni jälitaja lubada)
+ Kustuta küpsised ja ka lehitsemise ajalugu
+
+
+%1$s on Mozilla poolt toodetud. Meie missiooniks on edendada tervet ja avatud internetti.
+Rohkem teavet
]]>
+
+
+ Privaatsus ja turvalisus
+
+
+ Jälitamine, küpsised, andmete valikud
+
+
+ Vaikeotsingumootor, automaatne täitmine
+
+
+
+
+ %1$sest, abi
+
+
+ Täiustatud jälitamisvastane kaitse
+
+
+ Veebisisu
+
+
+ Äppide vahel vahetamine
+
+
+ Üldine
+
+
+ Vaikebrauser, keel
+
+
+ Andmete kogumine ja kasutamine
+
+ Otsing
+
+
+ Hangitakse otsingusoovitusi
+
+ %1$s saadab aadressiribale sisestatu sinu otsingumootorile
+
+
+ Vaikimisi
+
+
+ Otsingumootor
+
+
+ Sees
+
+
+ Väljas
+
+
+ URLide automaatne lõpetamine
+
+
+ Top saitide jaoks
+
+
+ Võimaldab %sel aadressiribal automaatselt lõpetada üle 450 populaarse URli.
+
+
+ Sinu lisatud saitide jaoks
+
+
+ Võimaldab %sel automaatselt lõpetada sinu lemmik URLe.
+
+
+ Saitide haldamine
+
+
+ Halda saite
+
+
+ + Lisa kohandatud URL
+
+
+ Sinu automaatse täitmise nimekiri:
+
+
+ Lisa URL
+
+
+ Kohandatud URLi lisamine
+
+
+ Lisa kohandatud URL
+
+
+ Lisa link automaatse täitmise nimekirja
+
+
+ Küpsised ja saidi andmed
+
+
+ Saadetavad andmed
+
+
+ Eemalda kohandatud URLid
+
+
+ Rohkem teavet
+
+
+ Lisa ja halda kohandatud automaatse täitmise URLe.
+
+
+ Lisatav URL
+
+
+ Aseta või sisesta URL
+
+
+ Näide: mozilla.org
+
+
+ Näide: example.com
+
+
+ Uus kohandatud URL lisatud.
+
+
+ Eemalda
+
+
+ Eemalda
+
+
+ Kontrolli sisestatud URLi uuesti.
+
+ Keel
+
+ Süsteemi vaikekeel
+
+ Privaatsus
+
+ Jälitajad blokitakse
+
+ Mõned reklaamid jälgivad saidi külastusi isegi siis, kui sa reklaamil ei klõpsa
+ Analüütilised jälitajad blokitakse
+ Kasutatakse veebis tehtavate tegevuste analüüsimiseks ja mõõtmiseks
+ Sotsiaalsed jälitajad blokitakse
+ Lisatud saitidele, et jälitada sinu külastusi ja kuvada funktsioone nagu jagamisnupud
+ Blokitakse muud jälitajad
+ Lubamine võib põhjustada mõne lehe ootamatu käitumise
+ Küpsised blokitakse
+
+
+ Tänan, ei soovi
+ Blokitakse ainult kolmanda osapoole jälitavad küpsised
+ Blokitakse ainult kolmanda osapoole küpsised
+ Blokitakse saidiülesed küpsised
+ Jah, palun
+
+
+ Rakenduse lahtilukustamiseks kasutatakse sõrmejälge
+
+ Kui oled juba lisanud otsetee või avanud kaardi, siis saad %se avada sõrmejäljega.
+
+
+ Salajasus
+
+ Äppide vahetajas varjatakse veebilehed ja ekraanipiltide tegemine blokitakse.
+
+ Turvalisus
+
+ Jõudlus
+ Veebifondid blokitakse
+
+ Võib põhjustada ikoonide või piltide puudumist
+
+ JavaScript blokitakse
+
+ Lehed võivad laadida kiiremini, kuid võivad käituda ootamatult
+
+
+ %1$s määratakse vaikebrauseriks
+
+ Mozilla
+ Saadetakse kasutusandmeid
+
+
+ Rohkem teavet
+
+
+ Mozilla püüab koguda ainult seda, mida vajame, et teha %1$s kõigi jaoks paremaks.
+
+
+ Privaatsusreeglid
+
+
+ %1$sest
+
+
+ Paigaldatud otsingumootorid
+
+
+ Otsingumootori määramine
+
+
+ Lähtesta vaikeotsingumootorid
+
+
+ + Lisa veel üks otsingumootor
+ Otsingumootorite eemaldamine
+ Eemalda
+
+ Lisa veel üks otsingumootor
+
+ Vali oma eelistatud otsingumootor:
+
+
+ Otsingumootori lisamine
+
+ Otsingumootori nimi
+ Kasutatav otsingustring
+ Salvesta
+
+
+ Näide: example.com/search/?q=%s
+
+ Uus otsingumootor lisatud.
+
+ Sisesta otsingumootori nimi
+ Paigaldatud otsingumootor juba kasutab sellist nime.
+
+ Sisesta otsingustring
+
+ Kontrolli, et otsingustring kattub näidises toodud formaadiga
+
+
+ Tühjenda sisend
+
+
+ Tühista
+
+
+ Kustuta lehitsemise ajalugu
+
+
+ Avatud kaarte: %1$s
+
+
+ Turvaline ühendus
+
+
+ Laadimine
+
+
+ Veebisait laaditud
+
+
+ Rohkem sätteid
+
+
+ Rohkemate sätete nupp
+
+
+ Liigu edasi
+
+
+ Laadi veebileht uuesti
+
+
+ Liigu tagasi
+
+
+ Peata veebilehe laadimine
+
+
+ Tagasi eelmise äpi juurde
+
+
+ Blokitud jälitajate arv
+
+
+ Bloki jälitajaid
+
+ Sinu õigused
+
+ Ava link teises äpis
+
+ Selle lingi avamiseks äpis %2$s võid lahkuda %1$sest.
+
+
+ Linki avada suutva äpi otsimine
+
+ Ükski sinu seadmes olev äpp ei saa seda linki avada. Võid lahkuda %1$sest, et otsida äpist %2$s sobivat äppi.
+
+ Kas väljuda privaatsest veebilehitsemisest?
+
+
+ Faili %1$s allalaadimine on lõpetatud
+
+
+ Ava
+
+ Serverit ei leitud
+
+
+ Tugevda oma privaatsust
+
+ Vii privaatne veebilehitsemine uuele tasemele. Bloki reklaamid ja muu sisu, mis võib sinu tegevust erinevatel saitidel jälitada ja lehtede laadimist aeglustada.
+
+
+ Sinu otsing, sinu moodi
+
+ Otsid midagi muud? Vali sätetes mõni teine vaikeotsingumootor.
+
+
+ Lisa otsetee oma avaekraanile
+
+
+ Naase kiiresti oma lemmiksaitidele %1$ses. Lihtsalt vali %1$se menüüst \"Lisa avaekraanile\".
+
+
+ Muuda privaatsus harjumuseks
+
+ Määra %1$s oma vaikebrauseriks ja kasuta teistest äppidest veebilehtede avamisel privaatse lehitsemise eeliseid.
+
+ Olgu, sain aru!
+ Jäta vahele
+ Järgmine
+
+
+ -
+
+
+ Lisa
+
+ JAH
+
+
+ Loobu
+
+ EI
+
+
+ Otsetee avaneb keelatud täiustatud jälitamisvastase kaitsega
+
+
+ Privaatse veebilehitsemise seanss
+
+
+ Teavitused võimaldavad sul %1$se seansi kustutada puudutusega. Sa ei pea äppi avama ega vaatama, mis brauseris töötab.
+
+
+ Kustuta lehitsemise ajalugu
+
+
+ Laadi alla Firefox
+
+
+
+
+
+ Mozilla avaliku litsentsi ja muude avatud lähtekoodi litsentside tingimuste alusel.]]>
+
+
+ Lisateave.]]>
+
+
+ litsentside alusel.]]>
+
+
+ GNU GPL v3 litsentsi all, mis on saadaval siin .]]>
+
+
+ Kasutajanimi
+ Parool
+ Tühjenda
+
+
+
+ Turvaline ühendus
+ Ebaturvaline ühendus
+
+ Verifitseerija: %1$s
+
+
+ Saidi turvalisus
+
+ URL on juba olemas
+
+
+ Otsi lehelt
+
+
+ Otsi lehelt
+
+
+ %1$d/%2$d
+
+ tulemus %1$d, kokku %2$d
+
+
+ Leia järgmine tulemus
+
+ Leia eelmine tulemus
+
+ Katkesta otsimine
+
+
+ Töölaua versioon
+
+
+ Töölaua versioon
+
+
+ URL kopeeritud
+
+
+ Arendaja tööriistad
+
+
+ Lingid avatakse äppides
+
+
+ Edasijõudnuile
+
+
+ Saitide õigused
+
+
+ Automaatne esitamine
+
+
+ Lubamiseks:
+
+
+ 1. Mine Androidi sätetesse
+
+
+ Load]]>
+
+
+ Mine sätetesse
+
+
+ %1$s olekusse SEES]]>
+
+
+ Kaamera
+
+
+ Mikrofon
+
+
+ Asukoht
+
+
+ Teavitused
+
+
+ DRMiga kaitstud sisu
+
+
+ Alati küsitakse
+
+
+ Blokitud
+
+
+ Lubatud
+
+
+ Keelatud Androidi poolt
+
+
+ Heli ja video on lubatud
+
+
+ Blokitakse ainult heli
+
+
+ Soovitatud
+
+
+ Blokitakse heli ja video
+
+
+ Uuringud
+
+
+ Firefox võib aegajalt paigaldada ja käivitada uuringuid.
+
+
+ Rohkem teavet
+
+
+ Muudatuste rakendamiseks äpp suletakse
+
+
+ Eemalda
+
+
+ Aktiivne
+
+
+ Lõpetatud
+
+
+ Kaugsilumine USB/Wi-Fi kaudu
+
+
+ Lukusta lahti
+
+
+ Kinnita sõrmejälge kasutades
+
+
+ Praeguse seansi jätkamiseks kasuta oma sõrmejälge.
+
+
+ Ava link uues seansis
+
+
+ Sõrmejälje ikoon
+
+
+ Sõrmejälge ei tuvastatud. Proovi uuesti.
+
+
+ Sõrm liikus liiga kiiresti. Proovi uuesti.
+
+
+ Kas kuvada otsingusoovitusi?
+
+
+ Soovituste saamiseks peab %1$s saatma aadressiribale sisestatud teksti otsingumootorile.
+
+
+ Ei
+
+
+ Jah
+
+
+ Mõnedel otsingumootoritel pole võimalik soovitusi kuvada.
+
+
+ Peida
+
+
+
+
+ Kas sait käitub ootamatult?\n
+ Proovi jälitamisvastane kaitse välja lülitada
+
+
+ Lisa avaekraanile]]>
+
+
+ Ava kõik lingid %1$ses\n
+ Määra %1$s vaikebrauseriks
+
+
+
+ Lõpeta enimkülastatud saitide URLid automaatselt\n
+ Puuduta pikalt mis tahes URLi aadressiribal
+
+
+
+ Ava link uuel kaardil\n
+ Puuduta pikalt mis tahes linki lehel
+
+
+
+ Lülita näpunäited avakuval välja
+
+
+ Avati uus kaart
+
+
+ Lülitu
+
+
+ Uuele kaardile lülitutakse koheselt
+
+
+ Ohtlikud ja petlikud saidid blokitakse
+
+ Tuntud petu-, ründe-, pahavara- ja soovimatu tarkvara saidid blokitakse.
+
+ Ainult HTTPS-režiim
+
+
+ Kõrgendatud turvalisuse nimel üritatakse saitidega ühenduda ainult HTTPSi krüptitud protokolli vahendusel.
+
+
+ Erandid
+
+ Sisu blokkimine on järgnevatel saitidel keelatud.
+
+ Eemalda
+
+ Eemalda kõik saidid
+
+
+ Küpsised blokitakse
+
+
+ Kas soovid küpsised blokkida?
+
+
+ Kaart jooksis kokku
+
+ Vabandust. Meil on selle kaardiga probleeme.
+
+ Privaatse brauserina ei salvesta me andmeid ja seetõttu ei saa seda kaarti taastada.
+
+ Sulge kaart
+
+
+ Mozillale saadetakse vearaport
+
+
+
+
+ Alates %s blokitud jälitajaid
+
+ Sisu
+
+ Reklaam
+
+ Sotsiaalmeedia
+
+ Analüütika
+
+ Täiustatud jälitamisvastane kaitse
+
+ Sellel saidil on kaitse VÄLJAS
+
+ Sellel saidil on kaitse SEES
+
+ Turvaline ühendus
+
+ Ühendus pole turvaline
+
+ Blokitavad jälitajad ja skriptid
+
+
+ Mine tagasi
+
+
+
+ Eemalda
+
+ Muuda nime
+
+ Nime muutmine
+
+ Otsetee nimi
+
+
+ Salvestatud ja jagatud pilte <b>ei</b> kustutata %1$se ajaloo kustutamisel
+
+
+
+ Teema
+
+ Hele
+
+ Tume
+
+ Seadistatud akusäästja poolt
+
+ Järgitakse seadme teemat
+
+
+
+ Sulge kaart
+
+
+ Sulge hüpikaken
+
+
+
+ Sa oled kaitstud!
+
+ Need vaikesätted pakuvad tugevat kaitset. Kuid nende muutmine konkreetselt sinu vajadustele on lihtne.
+
+ Peida
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-eu/strings.xml b/mobile/android/focus-android/app/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000000..9d8dfc274f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-eu/strings.xml
@@ -0,0 +1,1117 @@
+
+
+
+
+
+
+
+
+ Utzi
+
+ Ados
+
+ Gorde
+
+
+ Bilatu edo idatzi helbidea
+
+ Nabigatze pribatu automatikoa.\nNabigatu. Ezabatu. Errepikatu.
+
+
+ Zure nabigatze-historia ezabatu egin da.
+ Nabigazio-historia garbituta
+
+
+ Fitxaren nabigatze-historia ezabatu egin da.
+
+
+ Bilatu %1$s
+
+
+ Partekatu…
+
+
+ Eman gunearen arazoaren berri
+
+
+ Ireki %1$s nabigatzailean
+
+
+ Ireki honekin…
+
+
+ Gehitu hasierako pantailan
+
+
+ Gehitu lasterbideetara
+
+ Kendu lasterbideetatik
+
+
+ Ezarpenak
+ Honi buruz
+ Laguntza
+ Zure eskubideak
+
+
+ Blokeatutako elementuak
+
+
+ Hau itzaltzeak zenbait guneren arazoak konpon litzake
+
+
+ Edukia blokeatzea
+
+ Itzali zenbait gune konpontzeko
+
+
+ %1$s nabigatzaileak hornitua
+
+
+ Partekatu
+
+ Ezabatu nabigatze-historia?
+ Sakatu edo garbitu jakinarazpen hau zure nabigatze-historia ezabatzeko.
+
+
+ Sakatu edo pasatu zeharka jakinarazpen hau zure nabigatze-historia ezabatzeko.
+
+ Ezabatu nabigatze-historia
+
+
+ Ireki
+
+
+ Ezabatu eta ireki
+
+
+ Ezabatu
+
+
+ Ezabatu nabigatze-historia
+
+
+
+ Ezabatu eta ireki
+
+
+ Ezabatu eta ireki %1$s
+
+
+
+ Bilatu Focus erabiliz
+
+ Bilatu Klar erabliz
+
+ Bilatu Focus Beta erabiliz
+
+
+ Bilatu Focus Nightly erabiliz
+
+
+ %1$s(e)k agintea ematen dizu.
+Erabili nabigatzaile pribatu gisa:
+
+ Bilatu eta nabigatu aplikaziotik zuzenean
+ Blokeatu jarraipen-elementuak (edo eguneratu ezarpenak jarraipen-elementuak baimentzeko)
+ Ezabatu cookieak eta bilaketa- eta nabigatze-historia
+
+
+Mozillak egina da %1$s. Internet osasuntsu eta irekia sustatzea da gure misioa.
+Argibide gehiago
]]>
+
+
+ Pribatutasuna eta segurtasuna
+
+
+ Jarraipena, cookieak, datu-aukerak
+
+
+ Ezarri lehenespenak, osatze automatikoa
+
+
+
+
+ %1$s(r)i buruz, laguntza
+
+
+ Jarraipenaren babes hobetua
+
+
+ Web edukia
+
+
+ Aplikazioak aldatzea
+
+
+ Orokorra
+
+
+ Nabigatzaile lehenetsia, hizkuntza
+
+
+ Datuen bilketa eta erabilera
+
+ Bilaketa
+
+
+ Eskuratu bilaketa-iradokizunak
+
+ Helbide-barran idazten duzuna bilaketa-motorrera bidaliko du %1$s(e)k
+
+
+ Lehenetsia
+
+
+ Bilaketa-motorra
+
+
+ Aktibatuta
+
+
+ Desaktibatuta
+
+
+ URLen osatze automatikoa
+
+
+ Gune erabilienetarako
+
+
+ Gaituz gero, helbide-barran automatikoki osatuko ditu 450 URL ezagun baino gehiago %s(e)k.
+
+
+ Gehitzen dituzun guneetarako
+
+
+ Gaitu %s(e)k zure gogoko URLak automatikoki osatzeko.
+
+
+ Kudeatu guneak
+
+
+ Kudeatu guneak
+
+
+ + Gehitu URL perstonalizatua
+
+
+ Zure osatze automatikoko zerrenda:
+
+
+ Gehitu URLa
+
+
+ Gehitu URL perstonalizatua
+
+
+ Gehitu URL pertsonalizatua
+
+
+ Gehitu automatikoki osatzeko lotura
+
+
+ Cookieak eta guneetako datuak
+
+
+ Datu-aukerak
+
+
+ Kendu URL pertsonalizatuak
+
+
+ Argibide gehiago
+
+
+ Gehitu eta kudeatu automatikoki osatzeko URL pertsonalizatuak.
+
+
+ Gehitu beharreko URLa
+
+
+ Itsatsi edo idatzi URLa
+
+
+ Adibidea: mozilla.org
+
+
+ Adibidea: adibidea.eus
+
+
+ URL pertsonalizatu berria gehitu da.
+
+
+ Kendu
+
+
+ Kendu
+
+
+ Egiaztatu idatzi duzun URLa.
+
+ Hizkuntza
+
+ Sistemaren lehenetsia
+
+ Pribatutasuna
+ Blokeatu publizitatearen jarraipena
+ Zenbait iragarkik guneen bisiten jarraipena egiten dute, iragarkietan klik egiteko beharrik gabe ere
+ Blokeatu analitiken jarraipena
+ Sakatzea edo korritzea bezalako jarduerak bildu, analizatu eta neurtzeko erabiltzen da
+ Blokeatu sare sozialetako jarraipena
+ Guneetan txertatuta zure bisiten jarraipena egin eta partekatzeko botoien tankerako eginbideak bistaratzeko
+ Blokeatu bestelako edukien jarraipena
+ Gaituz gero, zenbait orrik espero gabeko portaera izan lezakete
+ Blokeatu cookieak
+
+
+ Ez, eskerrik asko
+ Blokeatu soilik hirugarrenen jarraipen-cookieak
+ Blokeatu soilik hirugarrenen cookieak
+ Blokeatu guneen arteko cookieak
+ Bai, mesedez
+
+
+ Erabili hatz-marka aplikazioa desblokeatzeko
+
+
+ Desblokeatu hatz-marka erabiliz lasterbideak gehitu badituzu edo webgune bat %s(e)n dagoeneko zabalik badago.
+
+
+ Modu isil-gordea
+
+ Ezkutatu web orriak aplikazioak aldatzean eta blokeatu pantaila-argazkiak ateratzea.
+
+ Segurtasuna
+
+ Errendimendua
+ Blokeatu webeko letra-tipoak
+
+ Ikonoak edo irudiak ez bistaratzea eragin lezake
+
+ Blokeatu JavaScript
+
+ Orriak azkarrago karga litezke baina espero gabeko portaera ere izan lezakete
+
+
+ Egin %1$s nabigatzaile lehenetsia
+
+ Mozilla
+ Bidali erabilera-datuak
+
+
+ Argibide gehiago
+
+
+ %1$s denontzat eskaini eta hobetzeko beharrezkoa dena soilik biltzen ahalegintzen da Mozilla.
+
+
+ Pribatutasun-oharra
+
+
+ Lizentziaren informazioa
+
+
+ Erabiltzen ditugun liburutegiak
+
+
+ %s | Kode irekiko liburutegiak
+
+
+ %1$s(r)i buruz
+
+
+ Instalatutako bilaketa-motorrak
+
+
+ Aukeratu bilaketa-motorra
+
+
+ Berrezarri bilaketa-motor lehenetsiak
+
+
+ + Gehitu beste bilaketa-motor bat
+ Kendu bilaketa-motorrak
+ Kendu
+
+ Gehitu beste bilaketa-motor bat
+
+ Hautatu bilaketa-motor hobetsia:
+
+
+ Gehitu bilaketa-motorra
+
+ Bilaketa-motorraren izena
+ Erabili beharreko bilaketa-katea
+ Gorde
+
+
+ Adibidea: adibidea.eus/search/?q=%s
+
+ Bilaketa-motor berria gehitu da.
+
+ Idatzi bilaketa-motorraren izena
+ Instalatutako bilaketa-motor batek izen hori darabil lehendik ere.
+
+ Idatzi bilaketa-katea
+
+ Egiaztatu bilaketa-katea adibidearen formatuarekin bat datorrela
+
+
+ Garbitu idatzitakoa
+
+
+ Baztertu
+
+
+ Ezabatu nabigatze-historia
+
+
+ Irekitako fitxak: %1$s
+
+
+ Konexio segurua
+
+
+ Kargatzen
+
+
+ Webgunea kargatuta
+
+
+ Aukera gehiago
+
+
+ Aukera gehiagorako botoia
+
+
+ Nabigatu aurrera
+
+
+ Berritu webgunea
+
+
+ Nabigatu atzera
+
+
+ Gelditu webgunea kargatzen
+
+
+ Itzuli aurreko aplikaziora
+
+
+ Blokeatutako elementu kopurua
+
+
+ Blokeatu jarraipena
+
+ Zure eskubideak
+
+ Ireki lotura beste aplikazio batean
+
+ %1$s utz dezakezu lotura hau %2$s aplikazioan irekitzeko.
+
+ Bilatu lotura ireki dezakeen aplikazio bat
+
+ Ez dago lotura hau irekitzeko gai den aplikaziorik zure gailuan. %1$s utz dezakezu lotura ireki dezaken aplikazio bat %2$s(e)n bilatzeko.
+
+ Irten nabigazio pribatutik?
+
+
+ %1$s amaituta
+
+
+ Ireki
+
+
+
+
+
+
+
+
+
+
+ Lasterbideetara gehituta!
+
+ Zerbitzaria ez da aurkitu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Itxi
+
+
+
+ Ongi etorri %1$s(e)ra!
+
+
+ Azkarra. Pribatua. Distraziorik gabea.
+
+
+ Hasi erabiltzen
+
+
+
+ %1$s ez da beste nabigatzaileak bezalakoa
+
+
+ Pribatutasun handiagorako, zure historia garbitzen dugu aplikazioa ixten duzunean.
+
+
+
+ Lehenetsi %1$s, irekitako lotura bakoitzarekin zure datuak babesteko.
+
+
+ Ezarri nabigatzaile lehenetsi gisa
+
+
+ Saltatu
+
+
+
+ Indartu zure pribatutasuna
+
+ Eraman nabigatze pribatua hurrengo mailara. Blokeatu iragarkiak eta webguneetan zehar zure jarraipena egin edo orrien karga-denbora makal dezaketen edukiak.
+
+
+ Bilaketa, zure erara
+
+ Zerbait desberdinaren bila? Aukeratu beste bilaketa-motor lehenetsi bat ezarpenetan.
+
+
+ Gehitu lasterbideak zure hasierako pantailan
+
+ Itzuli azkar batean zure gogoko guneetara %1$s(r)en. Hautatu \'Gehitu hasierako pantailan\' %1$s menutik.
+
+
+ Egizu pribatutasuna zure ohitura
+
+ Ezarri %1$s nabigatzaile lehenetsi gisa eta eskuratu nabigatze pribatuaren onurak beste aplikazioetatik web orriak irekitzean.
+
+ Ulertuta!
+ Saltatu
+ Hurrengoa
+
+
+ -
+
+
+ Gehitu
+
+ BAI
+
+
+ Utzi
+
+ EZ
+
+
+ Lasterbidea jarraipenaren babes hobetua desgaituta duela irekiko da
+
+
+ Nabigatze pribatuko saioa
+
+
+ Jakinarazpenetatik zure %1$s saioa ezaba dezakezu sakatzearekin soilik. Horretarako ez daukazu aplikazioa ireki edo nabigatzailean dagoena ikusi beharrik.
+
+
+ Ezabatu nabigatze-historia
+
+
+ Deskargatu Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License eta kode irekiko beste lizentzien baldintzapean.]]>
+
+
+ hemen aurki daitezke.]]>
+
+
+ lizentzietan.]]>
+
+
+ GNU General Public License v3 lizentziapeko aparteko lan independente gisa eta eskuragarri dago hemen .]]>
+
+
+ Erabiltzaile-izena
+ Pasahitza
+ Garbitu
+
+
+
+ Konexio segurua
+ Konexio ez-segurua
+
+ Egiaztatzailea: %1$s
+
+
+ Gunearen segurtasuna
+ URLa badago lehendik ere
+
+
+ Bilatu orrian
+
+
+ Bilatu orrian
+
+
+ %2$d/%1$d
+
+ %2$d(e)tik %1$d
+
+
+ Bilatu hurrengo emaitza
+
+ Bilatu aurreko emaitza
+
+ Baztertu orrian bilatzea
+
+
+
+
+ Mahaigaineko gunea
+
+
+ Mahaigainerako gunea
+
+
+ URLa kopiatu da
+
+
+ Garatzaile-tresnak
+
+
+ Ireki loturak aplikazioetan
+
+
+ Aurreratua
+
+
+ Gunearen baimenak
+
+
+ Cookie iragarki-banden murrizpena
+
+
+ Aktibatuta
+
+
+ Desaktibatuta
+
+
+ Cookie iragarki-banden murrizpena
+
+
+ Ikusi iragarki-banda gutxiago ahal denean cookie eskaerak automatikoki baztertuz.
+
+ -->
+ Cookie iragarki-banden murrizpena
+
+
+ Aktibatuta gune honetarako
+
+
+ Une honetan gune honetarako euskarririk ez
+
+
+ Desaktibatuta gune honetarako
+
+
+ Cookie iragarki-banden murrizpena
+
+
+ Desaktibatuta gune honetarako
+
+
+ Aktibatuta gune honetarako
+
+
+ Aktibatu cookie iragarki-banden murrizpena %1$s gunerako?
+
+
+ Desaktibatu cookie iragarki-banden murrizpena %1$s gunerako?
+
+
+ %1$s(e)k gune honetako cookieak garbitu eta orria berrituko du. Cookie guztiak garbitzean, saioak amaitu edo erosketa-orgak hustu litezke.
+
+
+ %1$s automatikoki saia daiteke cookie-eskaerak ukatzen.
+
+
+ Cookie iragarki-banden murrizpenak oraindik ez du gune honetarako euskarririk. Gure taldeak webgune hau berrikusi eta etorkizunean bere euskarria gehitzea eskatu nahi duzu?
+
+
+ Utzi
+
+
+ Eskatu euskarria
+
+
+ Gunearen euskarria gehitzeko eskaera bidalita.
+
+
+ Gunearen euskarria gehitzeko eskaera bidalita.
+
+
+
+ %1$s cookie eskaerak ukatzen saiatzen da cookie iragarki-banda gogaikarriak baztertzeko.\n\nKudeatu cookie iragarki-banden hobespenak %2$s.
+
+
+ ezarpenetan
+
+
+ Erreprodukzio automatikoa
+
+
+ Baimentzeko:
+
+
+ 1. Zoaz Android sistemako ezarpenetara
+
+
+ Baimenak]]>
+
+
+ Joan ezarpenetara
+
+
+ %1$s piztuta egon dadin]]>
+
+
+ Kamera
+
+
+ Mikrofonoa
+
+
+ Kokalekua
+
+
+ Jakinarazpena
+
+
+ DRM bidez kontrolatutako edukia
+
+
+ Galdetu baimentzeko
+
+
+ Blokeatuta
+
+
+ Baimenduta
+
+
+ Androidek blokeatuta
+
+
+ Baimendu audioa eta bideoa
+
+
+ Blokeatu audioa soilik
+
+
+ Gomendatua
+
+
+ Blokeatu audioa eta bideoa
+
+
+ Esperimentuak
+
+
+ Noizean behin esperimentuak instala eta exekuta litzake Firefoxek.
+
+
+ Argibide gehiago
+
+
+ Aplikazioa itxi egingo da aldaketak aplikatzeko
+
+
+ Kendu
+
+
+ Aktibo
+
+
+ Burututa
+
+
+ USB/Wi-Fi bidezko urruneko arazketa
+
+
+ Desblokeatu
+
+
+ Berretsi hatz-marka erabiliz
+
+
+ Zure hatz-marka erabil dezakezu aplikazioaren saioarekin jarraitzeko.
+
+
+ Ireki lotura saio berrian
+
+
+ Hatz-markaren ikonoa
+
+
+ Ez da hatz-marka ezagutu. Saiatu berriro.
+
+
+ Hatza azkarregi mugitu da. Saiatu berriro.
+
+
+ Erakutsi bilaketa-iradokizunak?
+
+
+ Iradokizunak lortzeko, helbide-barran idazten duzuna bilaketa-motorrari bidali behar dio %1$s(e)k.
+
+
+ Ez
+
+
+ Bai
+
+
+ Zenbait bilaketa-motorrek ezin dituzte iradokizunak erakutsi.
+
+
+ Baztertu
+
+
+
+
+ Guneak espero gabeko portaera du?\n Saiatu jarraipenaren babesa itzaltzen
+
+
+ Gehitu hasierako pantailan]]>
+
+
+ Ireki lotura guztiak %1$s aplikazioarekin\n Egin %1$s nabigatzaile lehenetsia
+
+
+ Osatu automatikoki gehien bisitatzen dituzun guneetako URLak\n Mantendu sakatuta helbide-barran edozein URL
+
+
+ Ireki lotura fitxa berri batean\n Mantendu sakatuta orri bateko edozein lotura
+
+
+ Desgaitu hasierako pantailako aholkuak
+
+
+ Fitxa berria irekita
+
+
+ Aldatu
+
+
+ Pantaila osoko moduan sartzen
+
+
+ Aldatu berehala loturara fitxa berrian
+
+
+ Blokeatu arriskutsuak eta iruzurtiak izan litezkeen guneak
+
+ Blokeatu iruzurti eta erasotzaile gisa identifikatutako guneak, hala nola malwarea eta eskatu gabeko softwarea duten guneak.
+
+
+ HTTPS-Only modua
+
+
+ Automatikoki saiatzen da guneetara konektatzen HTTPS zifratze-protokoloa erabiliz, segurtasun gehiago lortzeko.
+
+
+ Salbuespenak
+
+ Edukia blokeatzea desgaitu duzu gune hauetarako.
+
+ Kendu
+
+ Kendu webgune guztiak
+
+
+ Blokeatu cookieak
+
+
+ Cookieak blokeatu nahi dituzu?
+
+
+ Fitxak huts egin du
+
+ Barkatu. Arazo bat izaten ari gara fitxa honekin.
+
+ Nabigatzaile pribatu gisa, inoiz ez dugu ezer gordetzen eta ezin dugu fitxa hau berreskuratu.
+
+ Itxi fitxa
+
+
+
+
+
+ Bidali hutsegite-txostena Mozillara
+
+
+
+
+ Blokeatutako jarraipen-elementuak %s datatik
+
+ Edukia
+
+ Iragarkiak
+
+ Sare sozialak
+
+ Analitikak
+
+ Jarraipenaren babes hobetua
+
+ Babesak inaktibo daude gune honetarako
+
+ Babesak aktibo daude gune honetarako
+
+ Konexioa segurua da
+
+ Konexioa ez da segurua
+
+ Blokeatu beharreko jarraipen-elementu eta scriptak
+
+
+ Joan atzera
+
+
+
+ Kendu
+
+
+ Berrizendatu
+
+ Berrizendatu
+
+
+ Lasterbidearen izena
+
+
+ Deskargatutako eta partekatutako irudiak <b>ez dira</b> ezabatuko %1$s(r)en historia ezabatzean.
+
+
+
+ Itxura
+
+ Argia
+
+ Iluna
+
+ Bateria-aurrezleak ezarrita
+
+ Erabili gailuaren itxura
+
+
+
+ Gune honek ez du HTTPS onartzen
+
+
+ Argibide gehiago
+ Aldatu ezarpen hau Ezarpenak > Pribatutasuna eta Segurtasuna > Segurtasuna atalean.]]>
+
+
+ Konexio ez-segurua
+
+
+
+ Aurretik zerbitzari honetara behar bezala konektatu bazara, errorea behin-behinekoa izan daiteke.
+ ]]>
+
+
+ Baten bat gunea ordezten saiatzen egon liteke eta jarraitzea arriskutsua izan liteke.
+
+ %1$sek ez du %2$s fidagarritzat jotzen ziurtagiriaren jaulkitzailea ezezaguna delako, ziurtagiria guneak berak sinatutakoa delako, edo zerbitzariak ez dituelako tarteko ziurtagiriak egoki bidaltzen.
+ ]]>
+
+
+
+ Itxi fitxa
+
+
+
+ Harrapatu dugu! Gunek honek zu espiatzea galarazi dugu. Sakatu edonoiz babesaren ikonoa ikusteko zer ari garen blokeatzen.
+
+
+ Itxi laster-leihoa
+
+
+
+ Babestuta zaude!
+
+ Ezarpen lehenetsi hauek babes sendoa eskaintzen dute. Erraza da halere ezarpenak zure beharretara moldatzea.
+
+ Baztertu
+
+
+ Sakatu hemen dena zakarrontzira botatzeko — historia, cookieak, dena — eta hasi berriro zerotik fitxa berri batean.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Itxi
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Bilaketa widgeta
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Nabigatze-historia garbituta! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Hasi nabigatze pribatuko saioa eta jarraipen-elementuak eta bestelako gauza txarrak blokeatu egingo ditugu zuk nabigatzen duzun bitartean.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Zure nabigatze pribatuaren menpe utziko dugu, baina hurrengoan azkarrago has zaitezke zure hasierako pantailan %1$s widgeta erabiliz.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Gehitu widgeta hasierako pantailan
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widgeta hasierako pantailan gehitu da
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-fa/strings.xml b/mobile/android/focus-android/app/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000000..20b4181212
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-fa/strings.xml
@@ -0,0 +1,857 @@
+
+
+
+
+
+
+
+
+ لغو
+
+ تایید
+
+ ذخیره
+
+
+ جستوجو کنید یا آدرسی را وارد کنید
+
+ مرور ناشناس خودکار.\nمرور کنید. پاک کنید. تکرار کنید.
+
+
+ تاریخچهٔ مرور شما پاک شد.
+ تاریخچهٔ مرور، پاک شد
+
+
+ تاریخچه زبانههای مرورگر پاک شده است.
+
+
+ جستوجو برای %1$s
+
+
+ اشتراکگذاری…
+
+
+ گزارش مشکل در پایگاه
+
+
+ بازکردن در %1$s
+
+
+ بازکردن…
+
+
+ افزودن به صفحهٔ خانه
+
+
+ افزودن به میانبرها
+
+ برداشتن از میانبرها
+
+ تنظیمات
+ درباره
+ راهنما
+ حقوق شما
+
+
+ ردیابهای مسدود شده
+
+
+ خاموش کردن این ممکن است بتواند تعدادی از مشکلات این سایت را حل کند
+
+
+ مسدود کردن محتوا
+
+ خاموش کردن برای حل مشکل چند سایت
+
+
+ نیرو گرفته توسط %1$s
+
+
+ اشتراکگذاری از طریق
+
+
+ پاک کردن تاریخچهٔ مرورگر
+
+
+ باز کردن
+
+
+ حذف و باز کن
+
+
+ پاک کردن
+
+
+ پاک کردن تاریخچهٔ مرورگر
+
+
+
+ پاک کردن و بازکردن
+
+
+ پاک کردن و باز کردن %1$s
+
+
+
+ جستجو در فوکوس
+
+
+ جستجو در فوکوس بتا
+
+ جستجو در Focus Nightly
+
+
+ %1$s شما را تحت کنترل قرار میدهد.
+از این به عنوان یک مرورگر خصوصی استفاده کنید:
+
+ مستقیما در برنامه جستوجو و مرور کنید
+ ردیابها را مسدود کنید (یا تنظیمات را برای اجازه دادن به ردیابها تغییر دهید)
+ کوکیها را مانند جستوجو و تاریخچه مرور پاک یا حذف کنید
+
+
+%1$s توسط موزیلا ساخته شده است. ماموریت ما تا رواج یک اینترنت سالم و باز است.
+بیشتر بدانید
]]>
+
+
+ حریم خصوصی و امنیت
+
+
+ ردیابی، کوکیها، انتخابهای داده
+
+
+ تنظیم پیشفرض، تکمیل خودکار
+
+
+
+
+ دربارهٔ %1$s، راهنما
+
+
+ محافظت پیشرفته در برابر ردیابی
+
+
+ محتوای وب
+
+
+ تعویض برنامهها
+
+
+ عمومی
+
+
+ مرورگر پیشفرض، زبان
+
+
+ گردآوری داده و موارد استفاده
+
+ جستوجو
+
+
+ دریافت پیشنهادات جستوجو
+
+ %1$s چیزی که شما در نوار آدرس وارد میکنید را برای موتور جستوجو شما ارسال خواهد کرد
+
+
+ پیشفرض
+
+
+ موتور جستوجو
+
+
+ روشن
+
+
+ خاموش
+
+
+ تکمیل خودکار نشانی
+
+
+ برای سایتهای برتر
+
+
+ فعال کردن %s برای تکمیل خودکار بیش از ۴۵۰ URL محبوب در نوار آدرس.
+
+
+ برای سایتهایی که شما اضافه میکنید
+
+
+ فعال کردن %s برای تکمیل خودکار URLهای محبوب شما.
+
+
+ مدیریت سایتها
+
+
+ مدیریت سایتها
+
+
+ + افزودن نشانی سفارشی
+
+
+ فهرست تکمیل خودکار شما:
+
+
+ افزودن آدرس اینترنتی
+
+
+ افزودن نشانی سفارشی
+
+
+ افزودن نشانی سفارشی
+
+
+ افزودن پیوند به تکمیل خودکار
+
+
+ کوکیها و دادههای پایگاه
+
+
+ انتخابهای داده
+
+
+ حذف نشانیهای سفارشی
+
+
+ بیشتر بدانید
+
+
+ افزودن و مدیریت تکمیل خودکار نشانیهای سفارشی.
+
+
+ نشانی برای افزودن
+
+
+ جایگذاری یا ورود نشانی
+
+
+ مثال: mozilla.org
+
+
+ مثال: example.com
+
+
+ نشانی سفارشی جدید اضافه شد
+
+
+ حذف
+
+
+ حذف
+
+
+ نشانی که وارد کردید را دوباره بررسی کنید.
+
+ زبان
+
+ پیشفرض سیستم
+
+ حریم خصوصی
+ مسدود کردن ردیابهای تبلیغاتی
+ برخی از تبلیغها بازدیدهای سایت را دنبال میکنند، حتّی اگر شما روی تبلیغها کلیک نکنید
+ مسدود کردن ردیابهای آماری
+ استفاده شده برای جمعآوری، تحلیل و اندازهگیری فعّالیتهایی مانند لمس کردن و پیمایش کردن
+ مسدود کردن ردیابهای شبکههای اجتماعی
+ جاسازی شده در سایتها برای دنبال کردن بازدیدهای شما و برای نشان دادن عملکردهایی مانند دکمههای به اشتراک گذاری
+ مسدود کردن سایر ردیابهای محتوا
+ فعّال کردنش ممکن است منجر به آن شود که برخی از صفحات بطور غیر منتظره عمل کنند
+ مسدود کردن کوکیها
+
+
+ خیر، متشکرم
+ فقط مسدود کردن کوکیهای ردیابی شخص ثالث
+ فقط مسدود کردن کوکیهای شخص ثالث
+ مسدود کردن کوکیهای بینپایگاهی
+ بله، لطفاً
+
+
+ استفاده از اثرانگشت برای بازکردن برنامه
+
+
+ بازکردن قفل به وسیله اثر انگشت اگر این میانبر را اضافه کرده اید یا پایگاه اینترنتی در حال حاضر در %s باز است.
+
+
+ حالت پنهان
+
+ هنگام تعویض برنامهها صفحههای وب را مخفی و گرفتن تصاویر صفحه را مسدود کن.
+
+ امنیت
+
+ کارایی
+ مسدود کردن قلمهای وب
+
+ ممکن است باعث بارگیری نشدن برخی از شمایلها و تصاویر بشود
+
+ مسدود کردن جاوا اسکریپت
+
+ صفحات احتمالاً سریعتر بارگیری میشوند، اما ممکن است به طرز غیر متنظره عمل کنند
+
+
+ تبدیل %1$s به مرورگر پیشفرض
+
+ موزیلا
+ ارسال اطلاعات استفاده
+
+
+ اطلاعات بیشتر
+
+
+ موزیلا تلاش میکند تنها اطلاعاتی که برای بهینهسازی %1$s برای همه به آنها نیاز دارد را جمعآوری کند.
+
+
+ نکات حریمخصوصی
+
+
+ درباره %1$s
+
+
+ موتورهای جستوجو نصب شده
+
+
+ انتخاب موتور جستوجو
+
+
+ بازآوری موتورهای جستوجو پیشفرض
+
+
+ + افزودن یک موتور جستجوی دیگر
+ حذف موتورهای جستوجو
+ حذف
+
+ افزودن موتور جستوجوی دیگر
+
+
+ گزینش موتور مورد نظر شما:
+
+
+ اضافه کردن موتور جستوجو
+
+ نام موتور جستوجو
+ رشتهٔ جستوجو جهت استفاده
+ ذخیره
+
+
+ مثال: example.com/search/?q=%s
+
+ موتور جستوجو جدید اضافه شد.
+
+ نام موتور جستوجو را وارد کنید
+ پیش از این یک موتور جستجوی نصب شده از این نام استفاده میکند.
+
+ رشتهٔ جستوجو را وارد کنید
+
+ بررسی کنید که رشته جستوجو با شکل و قالب نمونهٔ ارائه شده مطابقت دارد
+
+
+ پاک کردن وروردی
+
+
+ رد کردن
+
+
+ حذف تاریخچه مرور
+
+
+ زبانههای باز: %1$s
+
+
+ اتصال امن
+
+
+ در حال بارگیری
+
+
+ وبسایت بارگیری شد
+
+
+ گزینههای بیشتر
+
+
+ دکمهی گزینههای بیشتر
+
+
+ حرکت به جلو
+
+
+ بارگیری مجدد سایت
+
+
+ بازگشت به عقب
+
+
+ توقف بارگیری وبسایت
+
+
+ بازگشت به برنامهٔ قبل
+
+
+ تعداد دنبال کنندههای مسدود شده
+
+
+ مسدود کردن ردیابها
+
+ حقوق شما
+
+ باز کردن پیوند در برنامهای دیگر
+
+ میتوانید از %1$s خارج شوید تا پیوند را در %2$s باز کنید.
+
+ برنامهای برای باز کردن پیوند پیدا کنید
+
+ هیچکدام از برنامههای روی دستگاه شما امکان باز کردن این پیوند را ندارند. میتوانید از %1$s خارج شوید تا در %2$s برای برنامهای که بتواند اینکار را انجام دهد جستوجو کنید.
+
+ از مرور ناشناس خارج میشوید؟
+
+
+ %1$s پایانیافته است
+
+
+ باز کردن
+
+
+
+
+
+
+
+
+
+ کارگزار پیدا نشد
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ بستن
+
+
+
+ به %1$s خوش آمدید
+
+
+ سریع. خصوصی. بدون حواس پرتی.
+
+
+ شروع کنید
+
+
+
+ %1$s مانند سایر مرورگرها نیست
+
+
+ برای حفظ حریم خصوصی بیشتر، وقتی برنامه را می بندید، سابقه شما را پاک می کنیم.
+
+
+ قدرت حریمشخصی خود را افزایش دهید
+
+ مرور خصوصی را به سطحی بالاتر ببرید. تبلیغات و دیگر محتواهایی را مسدود کنید که میتوانند شما را در سراسر سایتها ردیابی و زمانهای بارگذاری صفحه را خراب کنند.
+
+
+ جستجوی شما، به روش شما
+
+ به دنبال چیز متفاوتی هستید؟ موتور جستجوی پیشفرض دیگری را در تنظیمات انتخاب کنید.
+
+
+ میانبرها را به صفحه خانگی خود اضافه کنید
+
+ در %1$s به سرعت به سایتهای محبوب خود برگردید. فقط «افزودن به صفحه خانگی» را از منوی %1$s انتخاب کنید.
+
+
+ حفظ حریم خصوصی را به یک عادت تبدیل کنید
+
+ %1$s را به عنوان مرورگر پیشفرض خود انتخاب کنید و وقتی از اپهای دیگر صفحههای وب را باز میکنید از مزایای مرور خصوصی بهرهمند شوید.
+
+ باشه، گرفتم!
+ رد کردن
+ بعدی
+
+
+ -
+
+
+ افزودن
+
+ بله
+
+
+ انصراف
+
+ خیر
+
+
+ میانبر بدون محافظت پیشرفته در برابر ردیابی گشوده خواهد شد
+
+
+ نشستِ مرور ناشناس
+
+
+ اعلانها امکان پاک کردن اطلاعات نشستِ %1$s را با یک ضربه به شما میدهند. لازم نیست که برنامه را باز کنید یا ببینید که چه چیزی در مرورگر در حال اجراست.
+
+
+ پاک کردن تاریخچهٔ مرور
+
+
+ دریافت فایرفاکس
+
+
+
+
+
+
+
+
+ پروانه عمومی موزیلا و سایر مجوزهای متن باز در اختیار شما قرار میگیرد.]]>
+
+
+ اینجا قابل دسترسی است.]]>
+
+
+ پروانههای آزاد و متنباز در دسترس است.]]>
+
+
+ نگارش 3 پروانه جامع همگانی گنو و اینجا در دسترس است.]]>
+
+
+ نام کاربری
+ کلمه عبور
+ پاک کردن
+
+
+
+ اتصال امن
+ اتصال ناامن
+
+ تایید شده توسط:%1$s
+
+
+ امنیت پایگاه اینترنتی
+ نشانی قبلاً وجود دارد
+
+
+ یافتن در صفحه
+
+
+ پیدا کردن در صفحه
+
+
+ %1$d/%2$d
+
+ %1$d از %2$d
+
+
+ پیدا کردن نتیجه بعدی
+
+ پیدا کردن نتیجه قبلی
+
+ رد کردن پیدا کردن در این صفحه
+
+
+
+
+ درخواست نسخه رومیزی پایگاه
+
+
+ نسخهی میزکار پایگاه اینترنتی
+
+
+ برداشتURL
+
+
+ ابزارهای توسعه دهنده
+
+
+ باز کردن پیوندها در برنامهها
+
+
+ پیشرفته
+
+
+ مطالعات
+
+
+ فایرفاکس ممکن است هر از گاهی افزونهای تحقیقی نصب و اجرا کند.
+
+
+ بیشتر بدانید
+
+
+ برنامه برای اعمال تغییرات بسته میشود
+
+
+ حذف
+
+
+ فعال
+
+
+ تکمیل شد
+
+
+ اشکال زدایی از طریق USB/Wi-Fi
+
+
+ باز کردن پیوند در یک نشست تازه
+
+
+ نشانه اثرانگشت
+
+
+ اثرانگشت شناسایی نشد. مجددا تلاش کنید.
+
+
+ انگشت خیلی سریع حرکت کرد. مجدد تلاش کنید.
+
+
+ پیشنهادهای جستوجو نمایش داده شوند؟
+
+
+ برای دریافت پیشنهادات، %1$s نیاز دارد تا چیزی که در نوار آدرس مینویسید را به موتور جستوجو ارسال کند.
+
+
+ خیر
+
+
+ بله
+
+
+ تعدادی از موتورهای جستوجو امکان نمایش پیشنهادات را ندارند.
+
+
+ رد کردن
+
+
+
+
+ سایت رفتار غیرمنتظرهای دارد؟\n محافظت از دنبال کردن را خاموش کنید
+
+
+ اضافه کردن به صفحه خانگی]]>
+
+
+ بازکردن هر پیوندی در %1$s\n تنظیم%1$s به عنوان مرورگر پیش فرض
+
+
+ پرکردن خودکارURLs برای سایت های پربازدید شما\n نگاه داشتن بیشتر انگشت بر روی هر URL در نوار آدرس برای انجام این کار
+
+
+ بازکردن یک پیوند در زبانه جدید\n نگاه داشتن بیشتر انگشت برای باز کردن پیوند در صفحه
+
+
+ خاموش کردن نکات راهنما بر روی صفحه آغازین
+
+
+ زبانهي جدیدی باز شده است
+
+
+ تعویض
+
+
+ تعویض بلافاصله پیوند در زبانههای جدید
+
+
+ مسدود سازی خطرات بالقوه و سایتهای فریبنده
+
+ مسدود کردن گزارش های فریبنده و حملات سایتها، بدافزارهای سایتها و نرم افزارهای ناخواسته سایتها.
+
+
+ حالت فقط HTTPS
+
+
+ استثناعات
+
+ شما مسدود سازی محتوا برای این سایت را غیرفعال کردهاید.
+
+ حذف
+
+ حذف تمام سایتها
+
+
+ مسدود کردن کوکیها
+
+
+ آیا میخواهید کوکیها را مسدود کنید؟
+
+
+ زبانه خراب شد
+
+ متاسفیم. با این زبانه مشکل داریم.
+
+ به عنوان یک مرورگر خصوصی نمی توانیم این زبانه را ذخیره و بازیابی کنیم.
+
+ بستن زبانه
+
+
+
+
+
+ ارسال گزارش خرابی ها به موزیلا
+
+
+
+
+ ردیابهای مسدود شده از %s
+
+ محتوا
+
+ تبلیغات
+
+ اجتماعی
+
+ تجزیه و تحلیل
+
+ محافظت پیشرفته در برابر ردیابی
+
+ محافظتها برای این سایت خاموش است
+
+ محافظتها برای این سایت روشن است
+
+ اتّصال امن است
+
+ اتّصال امن نیست
+
+ ردیابها و اسکریپتها برای مسدود کردن
+
+
+ بازگشت
+
+
+
+ حذف
+
+
+ تغییر نام
+
+
+ وقتی شما تاریخچه %1$s را پاک میکنید، تصاویر ذخیره یا همرسانی شده <b>پاک نخواهند شد</b>
+
+
+
+
+ تم
+
+ روشن
+
+ تاریک
+
+ تنظیم شده توسط ذخیره کننده باتری
+
+ پیروی از تم دستگاه
+
+
+
+
+ بستن زبانه
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-fi/strings.xml b/mobile/android/focus-android/app/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000000..34399986dd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-fi/strings.xml
@@ -0,0 +1,1119 @@
+
+
+
+
+
+
+
+
+ Peruuta
+
+ OK
+
+ Tallenna
+
+
+ Kirjoita osoite tai hakusana
+
+ Automaattisesti yksityistä selaamista.\nSelaa. Tyhjennä. Toista.
+
+
+ Selaushistoria tyhjennetty.
+ Selaushistoria tyhjennettiin
+
+
+ Välilehden selaushistoria on poistettu.
+
+
+ Hae %1$s
+
+
+ Jaa…
+
+
+ Ilmoita sivuston ongelmasta
+
+
+ Avaa sovelluksella %1$s
+
+
+ Avaa sovelluksella…
+
+
+ Lisää linkki aloitusnäyttöön
+
+
+ Lisää pikavalintoihin
+
+ Poista pikavalinnoista
+
+
+ Asetukset
+ Tietoja
+ Ohje
+ Oikeutesi
+
+
+ Seuraimia estetty
+
+
+ Tämän poistaminen käytöstä voi korjata joitakin sivuston ongelmia
+
+
+ Sisällön estäminen
+
+ Poista käytöstä korjataksesi joitain sivustoja
+
+
+ Toiminnon tarjoaa %1$s
+
+
+ Jaa palvelulla
+
+ Poistetaanko selaushistoria?
+ Napauta tai tyhjennä tämä ilmoitus tyhjentääksesi selaushistoriasi turvallisesti.
+
+
+ Napauta tai pyyhkäise tämä ilmoitus tyhjentääksesi selaushistoriasi turvallisesti.
+
+ Tyhjennä selaushistoria
+
+
+ Avaa
+
+
+ Tyhjennä ja avaa
+
+
+ Tyhjennä
+
+
+ Tyhjennä selaushistoria
+
+
+
+ Tyhjennä ja avaa
+
+
+ Tyhjennä ja avaa %1$s
+
+
+
+ Hae Focusissa
+
+ Hae Klarissa
+
+ Hae Focus Betassa
+
+ Hae Focus Nightlyssa
+
+
+ %1$s asettaa sinut ohjaimiin.
+Käytä sitä yksityiseen selaamiseen:
+
+ Hae ja selaa suoraan sovelluksessa
+ Estä seuranta (tai muokkaa asetuksia salliaksesi seuraimet)
+ Poista evästeet ja haku- sekä selaushistoria
+
+
+%1$s on Mozillan kehittämä. Tehtävämme on edesauttaa tervettä, avointa Internetiä
+Lue lisää
]]>
+
+
+ Tietosuoja ja turvallisuus
+
+
+ Seuranta, evästeet, kerätyt tiedot
+
+
+ Aseta oletus, automaattinen täydennys
+
+
+
+
+ Tietoja: %1$s, ohje
+
+
+ Tehostettu seurannan suojaus
+
+
+ Verkkosisältö
+
+
+ Sovellusten vaihto
+
+
+ Yleiset
+
+
+ Oletusselain, kieli
+
+
+ Tietojen kerääminen & käyttö
+
+ Haku
+
+
+ Nouda hakuehdotuksia
+
+ %1$s lähettää osoiteriville kirjoittamasi tiedot hakukoneelle
+
+
+ Oletus
+
+
+ Hakukone
+
+
+ Päällä
+
+
+ Pois päältä
+
+
+ Osoitteen automaattinen täydennys
+
+
+ Ykkössivustoille
+
+
+ Ota käyttöön, jotta %s voi täydentää automaattisesti yli 450 suosittua osoitetta osoitepalkkiin.
+
+
+ Lisäämillesi sivustoille
+
+
+ Ota käyttöön, jotta %s voi täydentää automaattisesti suosikkiosoitteesi.
+
+
+ Hallitse sivustoja
+
+
+ Hallitse sivustoja
+
+
+ + Lisää mukautettu osoite
+
+
+ Automaattisen täydennyksen lista:
+
+
+ Lisää osoite
+
+
+ Lisää mukautettu osoite
+
+
+ Lisää mukautettu osoite
+
+
+ Lisää linkki automaattisesti täydennettäväksi
+
+
+ Evästeet ja sivustotiedot
+
+
+ Kerätyt tiedot
+
+
+ Poista mukautettu osoite
+
+
+ Lue lisää
+
+
+ Lisää ja hallitse mukautettuja osoitteita.
+
+
+ Lisättävä osoite
+
+
+ Liitä tai kirjoita osoite
+
+
+ Esimerkki: mozilla.org
+
+
+ Esimerkki: esimerkki.fi
+
+
+ Uusi mukautettu osoite lisätty.
+
+
+ Poista
+
+
+ Poista
+
+
+ Tarkista kirjoittamasi osoite.
+
+ Kieli
+
+ Järjestelmän oletus
+
+ Tietosuoja
+ Estä mainosseuraimet
+ Jotkin mainokset seuraavat sivustokäyntejä, vaikka et painaisi mainoksia
+ Estä analytiikkaseuraimet
+ Käytetään keräämään, analysoimaan ja mittaamaan toimintoja, kuten napautuksia ja vierityksiä
+ Estä sosiaalisen median seuraimet
+ Upotettu sivustoihin vierailujen seuraamiseksi ja toimintojen, kuten jakopainikkeiden, näyttämiseksi
+ Estä muut sisältöseuraimet
+ Tämän käyttöönotto saattaa johtaa joidenkin sivustojen odottamattomaan käytökseen
+ Estä kaikki evästeet
+
+
+ Ei kiitos
+ Estä vain kolmannen osapuolen seurantaevästeet
+ Estä vain kolmannen osapuolen evästeet
+
+ Estä sivustorajat ylittävät evästeet
+ Kyllä kiitos
+
+
+ Avaa sovellus sormenjäljellä
+
+
+ Avaa lukitus sormenjäljellä, jos olet lisännyt pikakuvakkeita tai kun verkkosivusto on jo auki %s -selaimessa.
+
+
+ Häivetila
+
+ Piilota verkkosivut vaihdettaessa sovellusta ja estä kuvakaappausten ottaminen.
+
+ Turvallisuus
+
+ Suorituskyky
+ Estä verkkosivun kirjasinlajit
+
+ Saattaa aiheuttaa puuttuvia kuvakkeita tai kuvia
+
+ Estä JavaScript
+
+ Sivut voivat latautua nopeammin, mutta voivat käyttäytyä odottamattomasti
+
+
+ Aseta %1$s oletusselaimeksi
+
+ Mozilla
+ Lähetä käyttötilastoja
+
+
+ Lisätietoja
+
+
+ Mozilla pyrkii keräämään vain tietoja, jotka ovat tarpeellisia %1$sen jakelun ja kehittämisen kannalta.
+
+
+ Tietosuojakäytäntö
+
+
+ Lisenssitiedot
+
+
+ Käyttämämme kirjastot
+
+
+ %s | Avoimen lähdekoodin kirjastot
+
+
+ Tietoja: %1$s
+
+
+ Asennetut hakukoneet
+
+
+ Valitse hakukone
+
+
+ Palauta oletushakukoneet
+
+
+ + Lisää toinen hakukone
+ Poista hakukone
+ Poista
+
+
+ Lisää toinen hakukone
+
+
+ Valitse ensisijainen hakukone:
+
+
+ Lisää hakukone
+
+ Hakukoneen nimi
+ Käytettävä hakulause
+ Tallenna
+
+
+ Esimerkiksi: example.com/search/?q=%s
+
+ Lisättiin uusi hakukone.
+
+ Anna hakukoneen nimi
+ Samanniminen hakukone on jo asennettu.
+
+ Kirjoita hakusana
+
+ Tarkista että hakusana vastaa esimerkin muotoa
+
+
+ Tyhjennä teksti
+
+
+ Hylkää
+
+
+ Tyhjennä selaushistoria
+
+
+ Välilehtiä avoinna: %1$s
+
+
+ Suojattu yhteys
+
+
+ Ladataan
+
+
+ Verkkosivu ladattu
+
+
+ Lisää asetuksia
+
+
+ Lisää asetuksia -painike
+
+
+ Siirry eteenpäin
+
+
+ Lataa verkkosivu uudelleen
+
+
+ Siirry taaksepäin
+
+
+ Keskeytä verkkosivun lataaminen
+
+
+ Palaa edelliseen sovellukseen
+
+
+ Estettyjen seuraimien määrä
+
+
+ Estä seuraimet
+
+ Oikeutesi
+
+ Avaa linkki toisessa sovelluksessa
+
+ Voit jättää %1$sin avataksesi tämän linkin sovelluksella %2$s.
+
+ Etsi sovellus, joka voi avata linkin
+
+ Yksikään laitteesi sovelluksista ei voi avata tätä linkkiä. Voit jättää %1$sin etsiäksesi oikeaa sovellusta %2$ssta.
+
+ Poistu yksityisestä selauksesta?
+
+
+ %1$s ladattiin
+
+
+ Avaa
+
+
+
+
+
+
+
+
+
+
+ Lisätty pikavalintoihin!
+
+ Palvelinta ei löytynyt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sulje
+
+
+
+ Tervetuloa %1$siin
+
+
+ Nopea. Yksityinen. Ei häiriötekijöitä.
+
+
+ Aloitetaan
+
+
+
+ %1$s ei ole kuten muut selaimet
+
+
+ Tyhjennämme historian yksityisyyden lisäämiseksi, kun suljet sovelluksen.
+
+
+
+ Aseta %1$s oletukseksi suojataksesi tietosi jokaisella avaamallasi linkillä.
+
+
+ Aseta oletusselaimeksi
+
+
+ Ohita
+
+
+
+ Tehosta yksityisyyttäsi
+
+ Vie yksityinen selaus uudelle tasolle. Estä mainoksia ja muuta sisältöä, jotka voivat seurata sinua eri sivustoilla ja hidastaa sivujen latausaikoja.
+
+
+ Hakusi haluamallasi tavalla
+
+ Haetko jotain erilaista? Valitse asetuksista toinen oletushakukone.
+
+
+ Lisää linkki aloitusnäyttöön
+
+ Avaa suosikkisivusi nopeasti %1$sissa. Valitse \"Lisää linkki aloitusnäyttöön\" %1$s -valikosta.
+
+
+ Tee yksityisyydestä verkossa tapa
+
+ Aseta %1$s oletusselaimeksesi ja hyödy yksityisestä selaamisesta kun avaat verkkosivuja muista sovelluksista.
+
+ OK, selvä!
+ Ohita
+ Seuraava
+
+
+ -
+
+
+ Lisää
+
+
+ KYLLÄ
+
+
+ Peruuta
+
+
+ EI
+
+
+ Pikakuvake avautuu ilman tehostettua seurannan suojausta
+
+
+ Yksityinen selausistunto
+
+
+ Ilmoitusten avulla voit nollata %1$s istuntosi hipaisulla. Sinun ei tarvitse avata sovellusta nähdäksesi tiedot selaimestasi.
+
+
+ Tyhjennä selaushistoria
+
+
+ Lataa Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public Licensen ja muiden avoimen lähdekoodin lisenssien ehdoin.]]>
+
+
+ täältä.]]>
+
+
+ lisenssien ehtojen mukaisesti.]]>
+
+
+ GNU General Public License v3 -lisenssin mukaisia töitä, ja ne ovat saatavilla täältä .]]>
+
+
+ Käyttäjätunnus
+ Salasana
+ Tyhjennä
+
+
+
+ Suojattu yhteys
+ Suojaamaton yhteys
+
+ Todentaja: %1$s
+
+
+ Sivuston tietoturva
+ Osoite on jo olemassa
+
+
+ Etsi sivulta
+
+
+ Etsi sivulta
+
+
+ %1$d/%2$d
+
+ %1$d/%2$d
+
+
+ Etsi seuraava tulos
+
+ Etsi edellinen tulos
+
+ Sulje sivulta etsiminen
+
+
+
+
+ Sivuston työpöytäversio
+
+
+ Työpöytäsivusto
+
+
+ Osoite kopioitu
+
+
+ Kehittäjätyökalut
+
+
+ Avaa linkit sovelluksissa
+
+
+ Lisäasetukset
+
+
+ Sivustojen käyttöoikeudet
+
+
+ Evästeilmoitusten vähennys
+
+
+ Päällä
+
+
+ Pois
+
+
+ Evästeilmoitusten vähennys
+
+
+ Näe vähemmän banneri-ilmoituksia hylkäämällä evästepyynnöt automaattisesti, kun mahdollista.
+
+ -->
+ Evästeilmoitusten vähennys
+
+
+ KÄYTÖSSÄ tällä sivustolla
+
+
+ Sivusto ei ole tuettu tällä hetkellä
+
+
+ EI KÄYTÖSSÄ tällä sivustolla
+
+
+ Evästeilmoitusten vähennys
+
+
+ EI KÄYTÖSSÄ tällä sivustolla
+
+
+ KÄYTÖSSÄ tällä sivustolla
+
+
+ Haluatko ottaa evästeilmoitusten vähennystoiminnon käyttöön sivustolla %1$s?
+
+
+ Haluatko poistaa evästeilmoitusten vähennystoiminnon käytöstä sivustolla %1$s?
+
+
+ %1$s poistaa tämän sivuston evästeet ja päivittää sivun. Kaikkien evästeiden poistaminen saattaa kirjata sinut ulos tai tyhjentää ostoskorit.
+
+
+ %1$s voi yrittää hylätä automaattisesti evästepyynnöt.
+
+
+ Evästeilmoitusten vähennys ei tällä hetkellä tue tätä sivustoa. Haluatko pyytää tiimiämme tarkistamaan tämän sivuston ja lisäämään tuen tulevaisuudessa?
+
+
+ Peruuta
+
+
+ Pyydä tukea
+
+
+ Pyyntö tukea tätä sivustoa lähetetty.
+
+
+ Pyyntö tukea tätä sivustoa lähetetty.
+
+
+
+ %1$s yrittää hylätä evästepyynnöt ja siten piilottaa ärsyttävät evästeilmoitukset.\n\nHallinnoi evästeilmoitusasetuksia %2$s.
+
+
+ asetuksissa
+
+
+ Automaattinen toisto
+
+
+ Salliaksesi sen:
+
+
+ 1. Mene Androidin asetuksiin
+
+
+ Käyttöoikeudet]]>
+
+
+ Mene asetuksiin
+
+
+ %1$s PÄÄLLÄ-asentoon]]>
+
+
+ Kamera
+
+
+ Mikrofoni
+
+
+ Sijainti
+
+
+ Ilmoitus
+
+
+ DRM-suojattu sisältö
+
+
+ Kysy lupaa
+
+
+ Estetty
+
+
+ Sallittu
+
+
+ Estetty Androidin toimesta
+
+
+ Salli ääni ja video
+
+
+ Estä vain ääni
+
+
+ Suositeltu
+
+
+ Estä ääni ja video
+
+
+ Tutkimukset
+
+
+ Firefox saattaa asentaa ja suorittaa tutkimuksia silloin tällöin.
+
+
+ Lue lisää
+
+
+ Tämä sovellus sulkeutuu, jotta muutokset tulevat käyttöön
+
+
+ Poista
+
+
+ Käynnissä
+
+
+ Valmistuneet
+
+
+ Etävianjäljitys USB:n/Wi-Fi:n välityksellä
+
+
+ Avaa
+
+
+ Vahvista sormenjäljen käyttäminen
+
+
+ Voit käyttää sormenjälkeäsi jatkaaksesi nykyistä sovellusistuntoa.
+
+
+ Avaa linkki uudessa istunnossa
+
+
+ Sormenjäljen kuvake
+
+
+ Sormenjälkeä ei tunnistettu. Yritä uudelleen.
+
+
+ Sormi liikkui liian nopeasti. Yritä uudelleen.
+
+
+ Näytetäänkö hakuehdotukset?
+
+
+ Hakuehdotusten saamiseksi %1$sin on lähetettävä osoitekenttään kirjoittamasi teksti hakukoneelle.
+
+
+ Ei
+
+
+ Kyllä
+
+
+ Jotkin hakukoneet eivät voi näyttää ehdotuksia.
+
+
+ Hylkää
+
+
+
+
+ Käyttäytyykö sivusto odottamattomasti?\n Kokeile kytkeä seurannan suojaus pois päältä
+
+
+ Lisää linkki aloitusnäyttöön]]>
+
+
+ Avaa jokainen linkki selaimella %1$s\n Aseta %1$s oletusselaimeksi
+
+
+ Täydennä osoitteet sivustoille, joita käytät eniten\n Paina pitkään osoitekentässä olevia osoitteita
+
+
+ Avaa linkki uuteen välilehteen\n Paina pitkään mitä tahansa linkkiä sivulla
+
+
+ Älä näytä vinkkejä aloitusnäytössä
+
+
+ Uusi välilehti avattu
+
+
+ Vaihda
+
+
+ Siirrytään koko näytön tilaan
+
+
+ Siirry välilehteen heti linkin avaamisen jälkeen
+
+
+ Estä mahdollisesti vaaralliset ja petolliset sivustot
+
+
+ Estä ilmoitetut petolliset ja hyökkäävät sivustot, haittaohjelmasivustot ja ei-toivottuja ohjelmia tarjoavat sivustot.
+
+
+ Vain HTTPS -tila
+
+
+ Yrittää muodostaa automaattisesti yhteyden sivustoihin käyttämällä salattua HTTPS-protokollaa turvallisuuden parantamiseksi.
+
+
+ Poikkeukset
+
+ Olet poistanut sisällön eston käytöstä näiden sivustojen kohdalla.
+
+ Poista
+
+ Poista kaikki sivustot
+
+
+ Estä evästeet
+
+
+ Haluatko estää evästeet?
+
+
+ Välilehti kaatui
+
+ Valitettavasti tämän välilehden kanssa on ongelmia.
+
+ Koska kyseessä on yksityinen selain, emme tallenna välilehtiä, eikä tätä välilehteä voi siksi palauttaa.
+
+ Sulje välilehti
+
+
+ Lähetä kaatumisraportti Mozillalle
+
+
+
+
+ Estetyt seuraimet %s lähtien
+
+ Sisältö
+
+ Mainonta
+
+ Sosiaalinen
+
+ Analytiikka
+
+ Tehostettu seurannan suojaus
+
+ Suojaukset ovat POIS PÄÄLTÄ tällä sivustolla
+
+ Suojaukset ovat PÄÄLLÄ tällä sivustolla
+
+ Yhteys on suojattu
+
+ Yhteys ei ole suojattu
+
+
+ Estettävät seuraimet ja komentosarjat
+
+
+ Takaisin
+
+
+
+ Poista
+
+
+ Nimeä uudelleen
+
+ Nimeä uudelleen
+
+
+ Pikakuvakkeen nimi
+
+
+ Tallennettuja ja jaettuja kuvia <b>ei poisteta</b> kun tyhjennät %1$sin historian
+
+
+
+ Teema
+
+ Vaalea
+
+ Tumma
+
+ Asetettu virransäästön toimesta
+
+ Seuraa laitteen teemaa
+
+
+
+ Tämä sivusto ei tue HTTPS:ää
+
+
+ Lue lisää
+ Muuta tätä asetusta kohdassa Asetukset > Tietosuoja ja turvallisuus > Turvallisuus.]]>
+
+
+ Yhteys ei ole suojattu
+
+
+
+ Jos yhteyden muodostaminen palvelimeen on aiemmin onnistunut, vika voi olla väliaikainen.
+ ]]>
+
+
+ Jokin toinen osapuoli saattaa tekeytyä sivustoksi, jatkaminen voi olla riskialtista.
+
+ %1$s ei luota sivustoon %2$s , koska sen varmenteen myöntäjä on tuntematon, varmenne on itse allekirjoitettu tai palvelin ei lähetä oikeita välivarmenteita.
+ ]]>
+
+
+
+ Sulje välilehti
+
+
+
+ Kiinni jäi! Tämän sivuston vakoilu sinua kohtaan on lopetettu. Napauta kilpeä milloin tahansa nähdäksesi, mitä estämme.
+
+
+ Sulje ponnahdusikkuna
+
+
+
+ Olet suojattu!
+
+ Nämä oletusasetukset tarjoavat vahvan suojan. Asetuksia on helppo muokata vastaamaan erityisiä tarpeitasi.
+
+ Hylkää
+
+
+ Napauta tätä poistaaksesi kaiken – historian, evästeet, kaiken – ja aloita puhtaalta pöydältä uudella välilehdellä.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Sulje
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Hakuwidget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Selaushistoria tyhjennetty! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Aloita yksityinen selausistuntosi, niin estämme seuraimet ja muut huonot asiat.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Annamme sinulle rauhan yksityiseen selailuun, mutta halutessasi saat nopeamman lähdön ensi kerralla lisäämällä %1$s-widgetin aloitusnäytöllesi.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Lisää widget aloitusnäyttöön
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget lisätty aloitusnäyttöön
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-fr/strings.xml b/mobile/android/focus-android/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000000..5472048ce4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,1120 @@
+
+
+
+
+
+
+
+
+ Annuler
+
+ OK
+
+ Enregistrer
+
+
+ Recherche ou adresse
+
+ Navigation privée automatique.\nNaviguez. Effacez. Recommencez.
+
+
+ Votre historique de navigation a été effacé.
+
+ Historique de navigation effacé
+
+
+ L’historique de navigation de l’onglet a été effacé.
+
+
+ Rechercher %1$s
+
+
+ Partager…
+
+
+ Signaler un problème sur ce site
+
+
+ Ouvrir avec %1$s
+
+
+ Ouvrir avec…
+
+
+ Ajouter à l’écran d’accueil
+
+
+ Ajouter aux raccourcis
+
+ Supprimer des raccourcis
+
+
+ Paramètres
+ À propos
+ Aide
+ Droits de l’utilisateur
+
+
+ Traqueurs bloqués
+
+
+ Désactiver cette option peut résoudre certains problèmes du site
+
+
+ Blocage de contenu
+
+ Désactiver pour réparer certains sites
+
+
+ Fonctionne grâce à %1$s
+
+
+ Partager avec
+
+ Effacer l’historique de navigation ?
+ Appuyez sur cette notification ou supprimez-la pour effacer votre historique de navigation de façon sécurisée.
+
+
+ Appuyez sur cette notification ou faites-la glisser pour effacer votre historique de navigation de façon sécurisée.
+
+ Effacer l’historique de navigation
+
+
+ Ouvrir
+
+
+ Effacer et ouvrir
+
+
+ Effacer
+
+
+ Effacer l’historique de navigation
+
+
+
+ Effacer et ouvrir
+
+
+ Effacer et ouvrir %1$s
+
+
+
+ Rechercher dans Focus
+
+ Rechercher dans Klar
+
+ Rechercher dans Focus Beta
+
+ Rechercher dans Focus Nightly
+
+
+ %1$s vous aide à rester aux commandes.
+Utilisez-le comme un navigateur privé :
+
+Effectuez des recherches et naviguez depuis l’application
+Bloquez les traqueurs (ou modifiez les paramètres pour les autoriser)
+Effacez les cookies, ainsi que les historiques de navigation et de recherche
+
+
+%1$s est réalisé par Mozilla. Notre mission est de défendre l’ouverture et la bonne santé d’Internet.
+En savoir plus
]]>
+
+
+ Vie privée et sécurité
+
+
+ Pistage, cookies, données collectées
+
+
+ Moteur par défaut, saisie semi-automatique
+
+
+
+
+ À propos de %1$s, aide
+
+
+ Protection renforcée contre le pistage
+
+
+ Contenu web
+
+
+ Navigation entre les applications
+
+
+ Général
+
+
+ Navigateur par défaut, langue
+
+
+ Collecte de données et utilisation
+
+ Recherche
+
+
+ Obtenir des suggestions de recherche
+
+ %1$s enverra ce que vous saisissez dans votre barre d’adresse à votre moteur de recherche
+
+
+ Par défaut
+
+
+ Moteur de recherche
+
+
+ Activée
+
+
+ Désactivée
+
+
+ Saisie semi-automatique des adresses
+
+
+ Pour les sites les plus populaires
+
+
+ Activer pour que %s complète automatiquement plus de 450 adresses populaires dans la barre d’adresse.
+
+
+ Pour les sites que vous ajoutez
+
+
+ Activer pour que %s complète automatiquement vos adresses web favorites.
+
+
+ Gérer les sites
+
+
+ Gérer les sites
+
+
+ + Ajouter une adresse personnalisée
+
+
+ Votre liste de saisie semi-automatique :
+
+
+ Ajouter l’adresse
+
+
+ Ajouter une adresse personnalisée
+
+
+ Ajouter une adresse personnalisée
+
+
+ Ajouter un lien à la saisie semi-automatique
+
+
+ Cookies et données de sites
+
+
+ Données collectées
+
+
+ Supprimer des adresses personnalisées
+
+
+ En savoir plus
+
+
+ Ajouter et gérer des adresses personnalisées pour la saisie semi-automatique.
+
+
+ Adresse web à ajouter
+
+
+ Coller ou saisir une adresse web
+
+
+ Exemple : mozilla.org
+
+
+ Exemple : example.com
+
+
+ Nouvelle adresse personnalisée ajoutée.
+
+
+ Supprimer
+
+
+ Supprimer
+
+
+ Veuillez vérifier l’adresse saisie.
+
+ Langue
+
+ Valeur par défaut du système
+
+ Vie privée
+ Bloquer les traqueurs publicitaires
+ Certaines publicités pistent vos visites sur les sites, même sans cliquer sur les publicités
+ Bloquer les traqueurs de statistiques
+ Ils servent à collecter, analyser et mesurer les actions telles qu’appuyer sur l’écran ou faire défiler la page
+ Bloquer les traqueurs de réseaux sociaux
+ Ils sont insérés sur certains sites pour pister vos visites et afficher des fonctionnalités comme des boutons de partage
+ Bloquer les autres traqueurs de contenu
+ Activer cette option peut provoquer des problèmes sur certaines pages
+ Interdire les cookies
+
+
+ Non
+ Bloquer uniquement les cookies tiers utilisés pour le pistage
+ Bloquer les cookies tiers uniquement
+
+ Bloquer les cookies intersites
+ Oui
+
+
+ Utiliser une empreinte digitale pour déverrouiller l’application
+
+
+ Déverrouillez à l’aide de l’empreinte digitale si vous avez ajouté des raccourcis ou lorsqu’un site web est déjà ouvert dans %s.
+
+
+ Mode furtif
+
+ Masquer les pages web lorsque vous changez d’application et empêcher les captures d’écran.
+
+ Sécurité
+
+ Performances
+ Bloquer les polices web
+
+ Peut empêcher certaines icônes ou images de s’afficher
+
+ Bloquer JavaScript
+
+ Les pages peuvent se charger plus rapidement, mais peuvent aussi se comporter de manière inattendue
+
+
+ Définir %1$s comme navigateur par défaut
+
+ Mozilla
+ Envoyer des données d’utilisation
+
+
+ En savoir plus
+
+
+ Mozilla veille à collecter uniquement les données nécessaires afin d’offrir et d’améliorer %1$s pour tout le monde.
+
+
+ Politique de confidentialité
+
+
+ Informations de licence
+
+
+ Bibliothèques utilisées
+
+
+ %s | Bibliothèques open source
+
+
+ À propos de %1$s
+
+
+ Moteurs de recherche installés
+
+
+ Choisir le moteur de recherche
+
+
+ Restaurer les moteurs par défaut
+
+
+ + Ajouter un autre moteur de recherche
+ Supprimer des moteurs de recherche
+ Supprimer
+
+
+ Ajouter un autre moteur de recherche
+
+ Sélectionnez votre moteur de recherche favori :
+
+
+ Ajouter un moteur de recherche
+
+ Nom du moteur de recherche
+ Chaîne de recherche à utiliser
+ Enregistrer
+
+
+ Exemple : example.com/search/?q=%s
+
+ Nouveau moteur de recherche ajouté.
+
+ Saisir le nom du moteur de recherche
+ Un moteur de recherche du même nom est déjà installé.
+
+ Saisir une chaîne de recherche
+
+ Vérifiez que la chaîne de recherche suit le format de l’exemple
+
+
+ Effacer le texte saisi
+
+
+ Fermer
+
+
+ Effacer l’historique de navigation
+
+
+ Onglets ouverts : %1$s
+
+
+ Connexion sécurisée
+
+
+ Chargement…
+
+
+ Site web chargé
+
+
+ Options supplémentaires
+
+
+ Bouton pour afficher davantage d’options
+
+
+ Avancer dans l’historique
+
+
+ Actualiser la page web
+
+
+ Précédent
+
+
+ Arrêter le chargement du site web
+
+
+ Revenir à l’application précédente
+
+
+ Nombre de traqueurs bloqués
+
+
+ Bloquer les traqueurs
+
+ Droits de l’utilisateur
+
+ Ouvrir le lien dans une autre application
+
+ Vous allez quitter %1$s pour ouvrir ce lien dans %2$s.
+
+ Trouver une application capable d’ouvrir ce lien
+
+ Aucune des applications de votre appareil n’est capable d’ouvrir ce lien. Vous pouvez quitter %1$s pour rechercher dans %2$s une application qui en est capable.
+
+ Quitter la navigation privée ?
+
+
+ %1$s terminé
+
+
+ Ouvrir
+
+
+
+
+
+
+
+
+
+
+ Ajouté aux raccourcis !
+
+ Adresse introuvable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fermer
+
+
+
+ Bienvenue dans %1$s
+
+
+ Rapide. Confidentiel. Sans distractions.
+
+
+ Commencer
+
+
+
+ %1$s n’est pas comme les autres navigateurs
+
+
+ Nous effaçons l’historique quand vous fermez l’application pour plus de confidentialité.
+
+
+
+
+ Choisissez %1$s comme navigateur par défaut et protégez vos données à chaque lien que vous ouvrez.
+
+
+ Définir comme navigateur par défaut
+
+
+ Passer
+
+
+
+ Renforcez votre vie privée
+
+ La navigation privée passe au niveau supérieur. Dites adieu aux publicités et autres traqueurs qui vous espionnent et ralentissent le chargement des pages web.
+
+
+ Recherchez à votre manière
+
+ Vous recherchez quelque chose de différent ? Choisissez un autre moteur de recherche par défaut dans les paramètres.
+
+
+ Ajoutez des raccourcis sur votre écran d’accueil
+
+ Retournez voir vos sites préférés dans %1$s rapidement. Sélectionnez simplement « Ajouter à l’écran d’accueil » depuis le menu de %1$s.
+
+
+ Reprenez votre vie privée en main
+
+ Définissez %1$s en tant que navigateur par défaut et bénéficiez d’une navigation privée dès que vous ouvrez les pages web à partir d’autres applications.
+
+ OK
+ Ignorer
+ Suivant
+
+
+ -
+
+
+ Ajouter
+
+
+ OUI
+
+
+ Annuler
+
+
+ NON
+
+
+ Ce raccourci s’ouvrira avec la protection renforcée contre le pistage désactivée
+
+
+ Session de navigation privée
+
+
+ Vous pouvez effacer votre session %1$s d’un simple appui depuis les notifications. Vous n’avez pas besoin d’ouvrir l’application ni d’afficher ce qui s’exécute dans votre navigateur.
+
+
+ Effacer l’historique de navigation
+
+
+ Télécharger Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License et d’autres licences open source.]]>
+
+
+ ici.]]>
+
+
+ licences libres et open source.]]>
+
+
+ GNU General Public License v3 et qui sont disponibles ici .]]>
+
+
+ Nom d’utilisateur
+ Mot de passe
+ Effacer
+
+
+
+ Connexion sécurisée
+ Connexion non sécurisée
+
+ Vérifié par : %1$s
+
+
+ Sécurité du site
+ L’adresse existe déjà
+
+
+ Rechercher dans la page
+
+
+ Rechercher dans la page
+
+
+ %1$d/%2$d
+
+ %1$d sur %2$d
+
+
+ Trouver le résultat suivant
+
+ Trouver le résultat précédent
+
+ Fermer la recherche dans la page
+
+
+
+
+ Voir version ordinateur
+
+
+ Version ordinateur
+
+
+ Adresse web copiée
+
+
+ Outils de développement
+
+
+ Ouvrir les liens dans des applications
+
+
+ Avancé
+
+
+ Autorisations de site
+
+
+ Réduction des bannières de cookies
+
+
+ Activée
+
+
+ Désactivée
+
+
+ Réduction des bannières de cookies
+
+
+ Voir moins de bannières en rejetant automatiquement les demandes de cookies quand c’est possible.
+
+ -->
+ Réduction des bannières de cookies
+
+
+ ACTIVÉE pour ce site
+
+
+ Site actuellement non pris en charge
+
+
+ DÉSACTIVÉE pour ce site
+
+
+ Réduction des bannières de cookies
+
+
+ DÉSACTIVÉE pour ce site
+
+
+ ACTIVÉE pour ce site
+
+
+ Activer la réduction des bannières de cookies pour %1$s ?
+
+
+ Désactiver la réduction des bannières de cookies pour %1$s ?
+
+
+ %1$s effacera les cookies de ce site et actualisera la page. La suppression de tous les cookies peut vous déconnecter ou vider les paniers d’achats.
+
+
+ %1$s peut essayer automatiquement de refuser les demandes de dépôt de cookies.
+
+
+ Ce site n’est actuellement pas pris en charge par la réduction des bannières de cookies. Souhaitez-vous demander à notre équipe de vérifier ce site et d’ajouter sa prise en charge ultérieurement ?
+
+
+ Annuler
+
+
+ Demander sa prise en charge
+
+
+ La demande de prise en charge du site a été envoyée.
+
+
+ La demande de prise en charge du site a été envoyée.
+
+
+
+ %1$s essaie de refuser les demandes de cookies pour vous débarrasser des bannières de cookies pénibles.\n\nGérez vos préférences pour les bannières de cookies dans les %2$s.
+
+
+ paramètres
+
+
+ Lire automatiquement des éléments multimédia
+
+
+ Pour l’autoriser :
+
+
+ 1. Accédez aux paramètres Android
+
+
+ Autorisations]]>
+
+
+ Se rendre dans les paramètres
+
+
+ %1$s]]>
+
+
+ Appareil photo
+
+
+ Microphone
+
+
+ Localisation
+
+
+ Notifications
+
+
+ Contenu protégé par des DRM
+
+
+ Demander pour autoriser
+
+
+ Bloqué
+
+
+ Autorisé
+
+
+ Bloqué par Android
+
+
+ Autoriser l’audio et la vidéo
+
+
+ Bloquer l’audio uniquement
+
+
+ Recommandé
+
+
+ Bloquer l’audio et la vidéo
+
+
+ Études
+
+
+ Firefox peut installer et lancer des études de temps en temps.
+
+
+ En savoir plus
+
+
+ L’application se fermera pour appliquer les modifications
+
+
+ Supprimer
+
+
+ Études actives
+
+
+ Études terminées
+
+
+ Débogage distant par USB/Wi-Fi
+
+
+ Déverrouiller
+
+
+ Confirmez à l’aide de votre empreinte digitale
+
+
+ Vous pouvez utiliser votre empreinte digitale pour poursuivre la session courante de l’application.
+
+
+ Ouvrir le lien dans une nouvelle session
+
+
+ Icône d’empreinte digitale
+
+
+ Empreinte digitale non reconnue. Veuillez réessayer.
+
+
+ Votre doigt a bougé trop rapidement. Veuillez réessayer.
+
+
+ Afficher les suggestions de recherche ?
+
+
+ Pour fournir des suggestions, %1$s doit transmettre ce que vous saisissez dans la barre d’adresse au moteur de recherche.
+
+
+ Non
+
+
+ Oui
+
+
+ Certains moteurs de recherche ne peuvent pas afficher de suggestions.
+
+
+ Ignorer
+
+
+
+
+ Ce site ne se comporte pas comme prévu ?\nEssayer de désactiver la protection contre le pistage
+
+
+ Ajouter à l’écran d’accueil]]>
+
+
+ Ouvrir tous les liens dans %1$s\n Définir %1$s comme navigateur par défaut
+
+
+ Compléter automatiquement les URL des sites les plus visités\n Effectuez un appui long sur une URL dans la barre d’adresse
+
+
+ Ouvrir un lien dans un nouvel onglet\n Effectuez un appui long sur un lien dans une page
+
+
+ Désactiver les conseils sur l’écran de démarrage
+
+
+ Nouvel onglet ouvert
+
+
+ Afficher
+
+
+ Mode plein écran activé
+
+
+ Afficher immédiatement les liens ouverts dans de nouveaux onglets
+
+
+ Bloquer les sites potentiellement dangereux ou trompeurs
+
+ Bloquer les sites malveillants et trompeurs, les sites de logiciels malveillants et indésirables signalés.
+
+
+ Mode HTTPS uniquement
+
+
+ Essayer de se connecter automatiquement aux sites en utilisant le protocole de chiffrement HTTPS pour une sécurité accrue.
+
+
+ Exceptions
+
+ Vous avez désactivé le blocage de contenu pour ces sites web.
+
+ Supprimer
+
+ Supprimer tous les sites web
+
+
+ Bloquer les cookies
+
+
+ Voulez-vous bloquer les cookies ?
+
+
+ L’onglet a planté
+
+ Nous sommes désolés, nous avons un problème avec cet onglet.
+
+ Votre navigation était privée, nous n’avons pas enregistré cet onglet et ne pouvons donc pas le restaurer.
+
+ Fermer l’onglet
+
+
+
+
+
+ Envoyer un rapport de plantage à Mozilla
+
+
+
+
+ Traqueurs bloqués depuis le %s
+
+ Contenu
+
+ Publicité
+
+ Réseaux sociaux
+
+ Statistiques
+
+ Protection renforcée contre le pistage
+
+ Les protections sont désactivées pour ce site
+
+ Les protections sont activées pour ce site
+
+ Connexion sécurisée
+
+ Connexion non sécurisée
+
+ Traqueurs et scripts à bloquer
+
+
+ Retour
+
+
+
+ Supprimer
+
+
+ Renommer
+
+ Renommer
+
+
+ Nom du raccourci
+
+
+ Les images enregistrées ou partagées <b>ne sont pas</b> supprimées quand vous effacez l’historique de %1$s
+
+
+
+ Thème
+
+ Clair
+
+ Sombre
+
+ Défini par l’économie d’énergie de la batterie
+
+ Suivre le thème de l’appareil
+
+
+
+ Ce site ne prend pas en charge HTTPS
+
+
+ En savoir plus
+ Modifiez ce paramètre dans Paramètres > Vie privée et sécurité > Sécurité.]]>
+
+
+ Connexion non sécurisée
+
+
+
+Si vous vous êtes déjà connecté·e avec succès à ce serveur, l’erreur est peut-être temporaire.]]>
+
+
+ Quelqu’un pourrait essayer de se faire passer pour le site et continuer pourrait être risqué.
+
+%1$s ne fait pas confiance à %2$s car l’émetteur de son certificat est inconnu, le certificat est auto-signé ou le serveur n’envoie pas les bons certificats intermédiaires. ]]>
+
+
+
+ Fermer l’onglet
+
+
+
+ Mission accomplie ! Nous avons empêché ce site de vous espionner. Appuyez sur le bouclier à tout moment pour voir ce que nous avons bloqué.
+
+
+ Fermer la fenêtre popup
+
+
+
+ Vous êtes protégé·e !
+
+
+ Ces paramètres par défaut offrent une protection renforcée. Mais il est facile de modifier les paramètres pour répondre à vos besoins spécifiques.
+
+ Fermer
+
+
+ Appuyez ici pour tout effacer — historique, cookies, tout — et recommencer à zéro dans un nouvel onglet.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Fermer
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget de recherche
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Historique de navigation effacé ! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Démarrez votre session de navigation privée et nous bloquerons traqueurs et autres nuisances pendant votre navigation.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Nous allons vous laisser à votre navigation privée, mais démarrez plus vite les prochaines fois avec le widget %1$s sur votre écran d’accueil.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Ajouter le widget à l’écran d’accueil
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget ajouté à l’écran d’accueil
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-fur/strings.xml b/mobile/android/focus-android/app/src/main/res/values-fur/strings.xml
new file mode 100644
index 0000000000..6abc77ff22
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-fur/strings.xml
@@ -0,0 +1,1080 @@
+
+
+
+
+
+
+
+
+ Anule
+
+ Va ben
+
+ Salve
+
+
+ Cîr o inserìs une direzion
+
+
+ Navigazion privade automatiche.\nNavighe. Scancele. Ripet.
+
+
+ La tô cronologjie di navigazion e je stade scancelade.
+ Cronologjie di navigazion netade
+
+
+ La cronologjie di navigazion de schede e je stade scancelade.
+
+
+ Cîr %1$s
+
+
+ Condivît…
+
+
+ Segnale probleme cun chest sît
+
+
+ Vierç in %1$s
+
+
+ Vierç in…
+
+
+ Zonte a schermade principâl
+
+
+ Zonte aes scurtis
+
+ Gjave des scurtis
+
+
+ Impostazions
+ Informazions
+ Jutori
+ I tiei dirits
+
+
+ Spiis blocadis
+
+
+ Disativant cheste opzion tu podaressis risolvi cualchi probleme cul sît
+
+
+ Bloc dai contignûts
+
+
+
+ Disative par risolvi problemis cun cualchi sît
+
+
+ Potenziât di %1$s
+
+
+
+ Condivît vie
+
+ Scancelâ la cronologjie di navigazion?
+ Tocje o scancele cheste notifiche par eliminâ in mût sigûr la tô cronologjie di navigazion.
+
+
+ Tocje o fâs scori cheste notifiche par eliminâ in mût sigûr la tô cronologjie di navigazion.
+
+ Scancele la cronologjie di navigazion
+
+
+ Vierç
+
+
+ Scancele e vierç
+
+
+ Scancele
+
+
+ Scancele la cronologjie di navigazion
+
+
+
+ Scancele e vierç
+
+
+ Scancele e vierç %1$s
+
+
+ Cîr in Focus
+
+ Cîr in Klar
+
+ Cîr in Focus Beta
+
+ Cîr in Focus Nightly
+
+
+ %1$s ti met al comant.
+Doprilu come navigadôr privât:
+
+ Cîr e navighe dret de app
+ Bloche lis spiis (o inzorne lis impostazions par permetilis)
+ Scancele par eliminâ i cookies e la cronologjie des ricercjis e di navigazion
+
+
+%1$s al è realizât di Mozilla. La nestre mission e je chê di promovi un internet plui san e viert.
+Plui informazions
]]>
+
+
+ Riservatece e sigurece
+
+
+ Sieltis par gjestî spiis, cookies e dâts
+
+
+ Stabilìs predefinît, completament automatic
+
+
+
+
+ Informazions su %1$s, jutori
+
+
+ Protezion miorade da lis spiis
+
+
+ Contignûts web
+
+
+ Cambi tra lis aplicazions
+
+
+ Gjenerâl
+
+
+ Navigadôr predefinît, lenghe
+
+
+ Racuelte e utilizazion dai dâts
+
+ Ricercje
+
+
+ Oten sugjeriments di ricercje
+
+ %1$s al inviarà al motôr di ricercje ce che tu scrivis te sbare de direzion
+
+
+ Predefinît
+
+
+ Motôr di ricercje
+
+
+ Ativât
+
+
+ Disativât
+
+
+ Completament automatic dal URL
+
+
+ Pai sîts principâi
+
+
+ Ative par completâ in automatic plui di 450 direzions popolârs te sbare de direzion di %s.
+
+
+ Pai sîts che tu zontis
+
+
+ Ative par fâ in mût che %s al completi in automatic lis tôs direzions preferidis.
+
+
+ Gjestion sîts
+
+
+ Gjestìs i sîts
+
+
+ + Zonte direzion personalizade
+
+
+ La tô liste pal completament automatic:
+
+
+ Zonte URL
+
+
+ Zonte URL personalizât
+
+
+ Zonte URL personalizât
+
+
+ Zonte colegament pal completament automatic
+
+
+ Cookies e dâts di sîts
+
+
+ Sieltis pai dâts
+
+
+ Gjave direzions personalizadis
+
+
+ Plui informazions
+
+
+ Zonte e gjestìs lis direzions personalizadis pal completament automatic.
+
+
+ Direzion di zontâ
+
+
+ Tache o inserìs une direzion
+
+
+ Esempli: mozilla.org
+
+
+ Esempli: esempli.com
+
+
+ Zontade gnove direzion personalizade.
+
+
+ Gjave
+
+
+ Gjave
+
+
+ Dopli-control de direzion inseride.
+
+ Lenghe
+
+ Predefinide di sisteme
+
+ Riservatece
+ Bloche spiis des publicitâts
+ Cualchi publicitât ti spie pai sîts che tu visitis, ancje se no tu fasis clic su la publicitât
+ Bloche lis spiis analitichis
+ A son dopradis par racuei, analizâ e misurâ lis ativitâts come i scoriments e lis batuçadis/tocjadis su schermi
+ Bloche lis spiis dai social
+ Integradis sui sîts par stâ daûr a ce che tu visitis e par visualizâ lis funzions come i botons pe condivision
+ Bloche altris spiis di contignûts
+ La ativazion e podarès causâ compuartaments inspietâts e problemis pal funzionament par cualchi pagjine
+
+ Bloche i cookies
+
+
+ No, graciis
+ Bloche dome i cookies spiis di tiercis parts
+ Bloche dome i cookies di tiercis parts
+ Bloche i cookies inter-sîts
+ Sì, graciis
+
+
+ Dopre l’impront par sblocâ la app
+
+ Sbloche doprant l’impront se tu âs zontât scurtis o cuant che un sît web al è za viert in %s.
+
+
+ Invisibil
+
+ Plate lis pagjinis web cuant che tu passis di une aplicazion in chê altre e impedìs la acuisizion des schermadis.
+
+ Sigurece
+
+ Prestazions
+ Bloche caratars web
+
+ Al podarès puartâ a mancjancis di iconis o imagjins
+
+ Bloche JavaScript
+
+ Lis pagjinis si cjamaran plui sveltis, ma a podaressin ancje vê compuartaments inspietâts o erôrs
+
+
+ Rint %1$s il navigadôr predefinît
+
+ Mozilla
+ Invie dâts di utilizazion
+
+
+ Plui informazions
+
+
+ Mozilla al cîr di tirâ dongje dome i dâts che i coventin par dâ fûr e miorâ %1$s par ducj i utents.
+
+
+ Informative su la riservatece
+
+
+ Informazions su la licence
+
+
+ Librariis dopradis
+
+
+ %s | Librariis OSS
+
+
+ Informazions su %1$s
+
+
+ Motôrs di ricercje instalâts
+
+
+ Sielç il motôr di ricercje
+
+
+ Ripristine i motôrs di ricercje predefinîts
+
+
+ + Zonte un altri motôr di ricercje
+ Gjave motôrs di ricercje
+ Gjave
+
+ Zonte un altri motôr di ricercje
+
+ Selezione il motôr di ricercje preferît:
+
+
+ Zonte motôr di ricercje
+
+ Non dal motôr di ricercje
+ Stringhe di ricercje di doprâ
+ Salve
+
+
+ Esempli: esempli.com/search/?q=%s
+
+ Zontât gnûf motôr di ricercje.
+
+ Inserìs il non dal motôr di ricercje
+ Un motôr di ricercje instalât al sta za doprant chel non.
+
+ Inserî la stringhe di ricercje
+
+ Verifiche che la stringhe di ricercje e corispuindi al formât di esempli
+
+
+ Nete il test
+
+
+ Siere
+
+
+ Scancele la cronologjie di navigazion
+
+
+ Schedis viertis: %1$s
+
+
+ Conession sigure
+
+
+ Daûr a cjamâ
+
+
+ Sît web cjamât
+
+
+ Altris opzions
+
+
+ Boton altris opzions
+
+
+ Navighe indevant
+
+
+ Torne cjame il sît web
+
+
+ Torne indaûr
+
+
+ Ferme di cjamâ il sît web
+
+
+ Torne ae aplicazion precedente
+
+
+ Numar di spiis blocadis
+
+
+ Bloche spiis
+
+ I tiei dirits
+
+ Vierç colegament intune altre app
+
+ Tu puedis lassâ %1$s par vierzi chest colegament in %2$s.
+
+ Cjate une app che e pues vierzi il colegament
+
+ Nissune des aplicazions sul to dispositîf e rive a vierzi chest colegament. Tu puedis jessî di %1$s par cirî in %2$s une app adate.
+
+
+ Jessî de navigazion privade?
+
+
+ %1$s completât
+
+
+ Vierç
+
+
+ Zontât aes scurtis!
+
+ Impussibil contatâ il servidôr
+
+
+ Siere
+
+
+
+ Benvignûts su %1$s
+
+
+ Svelt. Privât. Cence distrazions.
+
+
+ Scomence
+
+
+
+ %1$s nol è come chei altris navigadôrs
+
+
+ O scancelìn la tô cronologjie ogni volte che tu sieris la app, par dâti plui riservatece.
+
+
+
+ Rint %1$s il to navigadôr predefinît par protezi i tiei dâts cuant che tu vierzis cualsisei colegament.
+
+
+ Met come navigadôr predefinît
+
+
+ Salte
+
+
+ Potenzie la tô riservatece
+
+ Puarte la navigazion privade a un nivel superiôr. Bloche la publicitât e altris contignûts che a puedin spiâti tra i sîts e morestâ il cjariament des pagjinis.
+
+
+ La ricercje, a la tô maniere
+
+ Stavistu cirint alc altri? Sielç tes impostazions un altri motôr di ricercje predefinît.
+
+
+ Zonte scurtis te tô schermade principâl
+
+
+ Torne in tic e tac sui tiei sîts preferîts cun %1$s. Ti baste selezionâ “Zonte a schermade principâl” dal menù di %1$s.
+
+
+ Trasforme la riservatece intune abitudin
+
+ Met %1$s come navigadôr predefinît e gjolt dai vantaçs de navigazion privade cuant che tu vierzis pagjinis web di altris aplicazions.
+
+ Va ben, capît!
+ Salte
+ Indenant
+
+
+ -
+
+
+ Zonte
+
+ SÌ
+
+
+ Anule
+
+ NO
+
+
+ Cheste scurte e vignarà vierte cu la Protezion miorade da lis spiis disativade
+
+
+ Session di navigazion privade
+
+
+ Lis notifichie ti permetin di scancelâ i dâts de session di %1$s cuntune tocjade. No ti covente vierzi la aplicazion o viodi ce che al è in esecuzion sul to navigadôr.
+
+
+ Scancele cronologjie di navigazion
+
+
+ Discjame Firefox
+
+
+
+
+
+ Mozilla Public License e altris licencis Open Source.]]>
+
+
+ achì.]]>
+
+
+ licencis libaris e open source.]]>
+
+
+ GNU General Public License v3 e disponibil achì .]]>
+
+
+ Non utent
+ Password
+ Nete
+
+
+
+ Conession sigure
+ Conession no sigure
+
+ Verificât di: %1$s
+
+
+ Sigurece dal sît
+
+ L’URL al esist za
+
+
+ Cjate te pagjine
+
+
+ Cjate te pagjine
+
+
+ %1$d/%2$d
+
+ %1$d di %2$d
+
+
+ Cjate risultât sucessîf
+
+ Cjate risultât precedent
+
+
+ Bandone ricercje
+
+
+ Domande sît par scritori
+
+
+ Sît par scritori
+
+
+ URL copiât
+
+
+ Struments di svilup
+
+
+ Vierç i colegaments tes aplicazions
+
+
+ Avanzadis
+
+
+ Permès dai sîts
+
+
+ Riduzion strissons pai cookies
+
+
+ Ativade
+
+
+ Disativade
+
+
+ Riduzion strissons pai cookies
+
+
+ Viôt mancul strissons refudant in automatic, se pussibil, lis richiestis dai cookies.
+
+ -->
+ Riduzion strissons pai cookies
+
+
+ ATIVADE par chest sît
+
+
+ Sît pal moment no supuartât
+
+
+ DISATIVADE par chest sît
+
+
+ Riduzion strissons pai cookies
+
+
+ DISATIVADE par chest sît
+
+
+ ATIVADE par chest sît
+
+
+ Ativâ la riduzion strissons dai cookies par %1$s?
+
+
+ Disativâ la riduzion strissons dai cookies par %1$s?
+
+
+ %1$s al netarà vie i cookies di chest sît e al inzornarà la pagjine. Al è pussibil che, netant vie ducj i cookies, tu vegnis disconetût dal sît opûr che i carei des spesis a sedin disvuedâts.
+
+
+ %1$s al pues provâ a refudâ in automatic lis richiestis dai cookies.
+
+
+ Pal moment chest sît nol è supuartât de Riduzion strissons pai cookies. Desideristu che il nestri grup al revisioni chest sît web e che un doman lu supuarti?
+
+
+ Anule
+
+
+ Domande supuart
+
+
+ Richieste di supuart pal sît inviade.
+
+
+ Richieste di supuart pal sît inviade.
+
+
+
+ %1$s al c ir di refudâ lis richiestis dai cookies par sierâ i fastidiôs strissons pai cookies.\n\nGjestìs lis preferencis pai strissons dai cookies in %2$s.
+
+ impostazions
+
+
+ Riproduzion automatiche
+
+
+ Par permetilu:
+
+
+ 1. Va tes impostazions di Android
+
+
+ Permès]]>
+
+
+ Va aes impostazions
+
+
+ %1$s su Permet]]>
+
+
+ Fotocjamare
+
+
+ Microfon
+
+
+ Posizion
+
+
+ Notifichis
+
+
+ Contignûts protets di DRM
+
+
+ Domande il permès
+
+
+ Blocât
+
+
+ Permetût
+
+
+ Blocât di Android
+
+
+ Permet audio e video
+
+
+ Bloche dome l’audio
+
+
+ Conseât
+
+
+ Bloche audio e video
+
+
+ Studis
+
+
+ Firefox al pues instalâ e puartâ indenant studis di cuant in cuant.
+
+
+ Plui informazions
+
+
+ La aplicazion e jessarà par aplicâ lis modifichis
+
+
+ Gjave
+
+
+ Atîfs
+
+
+ Completâts
+
+
+ Debug di lontan vie USB/Wi-Fi
+
+
+ Sbloche
+
+
+ Conferme doprant l’impront digjitâl
+
+
+ Tu puedis doprâ il to impront digjitâl par continuâ la session corinte de aplicazion.
+
+
+ Vierç colegament intune gnove session
+
+
+ Icone dal impront digjitâl
+
+
+ Impront digjitâl no ricognossût. Torne prove.
+
+
+ Dêt mot masse adore. Torne prove.
+
+
+ Mostrâ i sugjeriments di ricercje?
+
+
+ Par vê sugjeriments, %1$s al à di inviâ al motôr di ricercje ce che tu scrivis te sbare de direzion.
+
+
+ No
+
+
+ Sì
+
+
+ Cualchi motôr di ricercje nol pues mostrâ sugjeriments.
+
+
+ Ignore
+
+
+
+
+ Un sît mostrial un compuartament anomal?\n
+ Prove a disativâ la Protezion da lis spiis
+
+
+ Zonte a schermade principâl]]>
+
+
+ Vierç ducj i colegaments in %1$s\n
+ Met %1$s come navigadôr predefinît
+
+
+
+ Auto-complete i URLs pai sîts che tu dopris di plui\n
+ Ten fracât plui a dilunc un URL te sbare de direzion
+
+
+
+ Vierç un colegament intune gnove schede\n
+ Ten fracât plui a dilunc un colegament intune pagjine
+
+
+
+ Disative i sugjeriments te schermade iniziâl
+
+
+ Vierte gnove schede
+
+
+ Passe a
+
+
+ Daûr a jentrâ in modalitât plen schermi
+
+
+ Passe dret al colegament te gnove schede
+
+
+ Bloche i sîts potenzialmentri pericolôs e ingjanôs
+
+ Bloche i sîts che a son stâts segnalâts tant che malevui o ingjanôs, che a contegnin malware o software malvolût.
+
+ Modalitât Dome-HTTPS
+
+ Prove in automatic la conession ai sîts doprant il protocol di cifradure HTTPS par vê une sigurece plui fuarte.
+
+
+ Ecezions
+
+ Tu âs disativât il bloc dai contignûts par chescj sîts web.
+
+ Gjave
+
+ Gjave ducj i sîts
+
+
+ Bloche i cookies
+
+
+ Desideristu blocâ i cookies?
+
+
+ Schede colassade
+
+ Nus displâs. Al è vignût fûr un probleme cun cheste schede.
+
+
+ Sint un navigadôr privât, no salvìn mai e no podìn ripristinâ cheste schede.
+
+ Siere schede
+
+
+ Invie la segnalazion di colàs a Mozilla
+
+
+
+
+ Spiis blocadis dal/i %s
+
+ Contignûts
+
+ Publicitâts
+
+ Social
+
+ Statistichis
+
+ Protezion miorade da lis spiis
+
+ Lis protezions a son DISATIVADIS par chest sît
+
+ Lis protezions a son ATIVIS par chest sît
+
+ La conession e je sigure
+
+
+ La conession no je sigure
+
+ Spiis e scripts di blocâ
+
+
+ Torne indaûr
+
+
+
+ Gjave
+
+ Torne a nomenâ
+
+ Cambie non
+
+ Non de scurte
+
+
+ Lis imagjins salvadis e condividudis <b>no vignaran</b> eliminadis cuant che la cronologjie di %1$s e ven scancelade
+
+
+
+ Teme
+
+ Clâr
+
+ Scûr
+
+ Stabilît dal sparagn energjetic
+
+ Va daûr dal teme dal dispositîf
+
+
+ Chest sît nol supuarte HTTPS
+
+
+ Plui informazions
+ Modifiche cheste impostazion in Impostazions > Riservatece e sigurece > Sigurece.]]>
+
+
+ Conession no sigure
+
+
+
+ Se in passât al è stât pussibil conetisi a chest servidôr cun sucès, l’erôr al podarès sei temporani.
+ ]]>
+
+
+ Al podarès jessi che cualchidun al sta cirint di sostituîsi al sît e continuâ al podarès jessi un risi.
+
+ %1$s nol considere atendibil %2$s parcè che l’emitent dal so certificât nol è cognossût, il certificât al è auto-firmât opûr il servidôr nol sta mandant i certificâts intermedis juscj.
+ ]]>
+
+
+
+ Siere schede
+
+
+
+ Cjapât!! O vin impedît a chest sît di spiâti. Tocje il scût cuant che tu vûs par viodi ce che o stin blocant.
+
+
+ Siere barcon a comparse
+
+
+
+ Tu sês protet!
+
+ Chestis impostazions predefinidis a ufrissin une buine protezion. Ma al è facile modificâ lis impostazions par lâ incuintri aes tôs specifichis necessitâts.
+
+ Siere
+
+
+ Tocje achì par butâ vie dut — cronologjie, cookies, dut — e scomençâ dal principi suntune gnove schede.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Siere
+
+
+ Widget di ricercje
+
+
+ Cronologjie di navigazion netade! 🎉
+
+
+ Scomence la tô session di navigazion privade, intant nô o blocarìn lis spiis e altre brute robate.
+
+
+ Ti lassìn ae tô navigazion privade, ma la prossime volte visiti che tu puedis scomençâ in maniere plui svelte doprant il widget di %1$s te schermade principâl.
+
+
+ Zonte widget ae schermade principâl
+
+
+ Widget zontât ae pagjine principâl
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-fy-rNL/strings.xml b/mobile/android/focus-android/app/src/main/res/values-fy-rNL/strings.xml
new file mode 100644
index 0000000000..5bf34bf8fb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-fy-rNL/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ Annulearje
+
+ OK
+
+ Bewarje
+
+
+ Fier sykterm of adres yn
+
+ Automatysk privee sneupe.\nSneupe. Wiskje. Werhelje.
+
+
+ Jo sneupskiednis is wiske.
+ Navigaasjeskiednis wiske
+
+
+ Jo sneupskiednis fan de ljepblêden is wiske.
+
+
+ Sykje nei %1$s
+
+
+ Diele…
+
+
+ Websiteprobleem rapportearje
+
+
+ Iepenje yn %1$s
+
+
+ Iepenje yn…
+
+
+ Tafoegje oan startskerm
+
+
+ Oan fluchkeppelingen tafoegje
+
+ Fuortsmite út Fluchkeppelingen
+
+
+ Ynstellingen
+ Oer
+ Help
+ Jo rjochten
+
+
+ Trackers blokkearre
+
+
+ Dit útskeakelje kin problemen mei websites ferhelpe
+
+
+ Ynhâldsblokkearring
+
+ Utskeakelje, om problemen mei guon websites te ferhelpen
+
+
+ Mooglik makke troch %1$s
+
+
+ Diele fia
+
+ Navigaasjeskiednis wiskje?
+ Tik op dizze notifikaasje of slút him om jo navigaasjeskiednis feilich te wiskje.
+
+
+ Tik op dizze notifikaasje of fei der oerhinne om jo navigaasjeskiednis feilich te wiskjen.
+
+ Navigaasjeskiednis wiskje
+
+
+ Iepenje
+
+
+ Wiskje en iepenje
+
+
+ Wiskje
+
+
+ Navigaasjeskiednis wiskje
+
+
+
+ Wiskje & iepenje
+
+
+ Wiskje en %1$s iepenje
+
+
+
+ Sykje yn Focus
+
+ Sykje yn Klar
+
+ Sykje yn Focus Beta
+
+ Sykje yn Focus Nightly
+
+
+ %1$s jout jo de kontrôle.
+Gebrûk as in priveebrowser:
+
+ Streekrjocht fan de app út sykje en navigearje
+ Trackers blokkearje (of ynstellingen bywurkje om se ta te stean)
+ Wiskjen smyt sawol cookies as syk- en navigaasjeskiednis fuort
+
+
+%1$s wurdt makke troch Mozilla. Us missy is it fersoargjen fan in sûn en iepen ynternet.
+Mear ynfo
]]>
+
+
+ Privacy en befeiliging
+
+
+ Folgjen, cookies, gegevenskarren
+
+
+ As standert ynstelle, auto-oanfolje
+
+
+
+
+ Oer %1$s, help
+
+
+ Ferbettere beskerming tsjin folgjen
+
+
+ Webynhâld
+
+
+ Apps wikselje
+
+
+ Algemien
+
+
+ Standertbrowser, taal
+
+
+ Gegevenssamling en gebrûk
+
+ Sykje
+
+
+ Syksuggesties krije
+
+ %1$s sil wat jo yn de adresbalke yntype nei jo sykmasine stjoere
+
+
+ Standert
+
+
+ Sykmasine
+
+
+ Oan
+
+
+ Ut
+
+
+ URL’s automatysk oanfolje
+
+
+ Foar topwebsites
+
+
+ Skeakelje dit yn om %s automatysk mear as 450 populêre URL’s yn de adresbalke oanfolje te litten.
+
+
+ Foar websites dy’t jo tafoegje
+
+
+ Skeakelje dit yn om %s automatysk jo favorite URL’s oanfolje te litten.
+
+
+ Websites beheare
+
+
+ Websites beheare
+
+
+ + Oanpaste URL tafoegje
+
+
+ Jo list foar automatysk oanfoljen:
+
+
+ URL tafoegje
+
+
+ Oanpaste URL tafoegje
+
+
+ Oanpaste URL tafoegje
+
+
+ Keppeling foar automatysk oanfolje tafoegje
+
+
+ Cookies en websitegegevens
+
+
+ Gegevensopsjes
+
+
+ Oanpaste URL’s fuortsmite
+
+
+ Mear ynfo
+
+
+ Eigen URL’s foar automatysk oanfoljen tafoegje en beheare.
+
+
+ Ta te foegjen URL
+
+
+ Plak of fier URL yn
+
+
+ Foarbyld: mozilla.org
+
+
+ Foarbyld: example.com
+
+
+ Nije oanpaste URL tafoege.
+
+
+ Fuortsmite
+
+
+ Fuortsmite
+
+
+ Kontrolearje de ynfierde URL.
+
+ Taal
+
+ Systeemstandert
+
+ Privacy
+ Advertinsjetrackers blokkearje
+ Guon advertinsjes folgje websitebesiten, sels as jo net op de advertinsjes klikke
+ Analysetrackers blokkearje
+ Brûkt foar it sammeljen, analysearjen en mjitten fan aktiviteiten lykas tikken en skowen
+ Sosjale trackers blokkearje
+ Ynboud op websites om jo besiken te folgjen en funksjonaliteit as dielknoppen te toanen
+ Oare ynhâldstrackers blokkearje
+ Ynskeakeljen kin feroarsaakje dat guon siden ûnferwachts wurkje
+ Cookies blokkearje
+
+
+ Nee, tankewol
+ Allinnich tracker-cookies fan tredden blokkearje
+ Allinnich cookies fan tredden
+
+ Cross-sitecookies blokkearje
+ Ja, graach
+
+
+ Fingerôfdruk foar ûntskoatteljen fan app brûke
+
+
+ Untskoattelje mei fingerôfdruk as jo fluchtoetsen tafoege hawwe of as in website al iepen is yn %s.
+
+
+ Stealth
+
+ Websiden ferstopje by wikseljen fan apps en meitsjen fan skermôfdrukken blokkearje.
+
+ Befeiliging
+
+ Prestaasjes
+ Weblettertypen blokkearje
+
+ Kin ta ûntbrekkende piktogrammen of ôfbyldingen liede
+
+ JavaScript blokkearje
+
+ Siden kinne flugger laden wurde, mar kinne harren ek ûnferwachte hâlde en drage
+
+
+ %1$s standertbrowser meitsje
+
+ Mozilla
+ Brûksgegevens ferstjoere
+
+
+ Mear ynfo
+
+
+ Mozilla stribbet dernei allinnich te sammeljen wat nedich is om %1$s foar elkenien oan te bieden en te ferbetterjen.
+
+
+ Privacyferklearring
+
+
+ Lisinsje-ynformaasje
+
+
+ Brûkte biblioteken
+
+
+ %s | OSS-biblioteken
+
+
+ Oer %1$s
+
+
+ Ynstallearre sykmasinen
+
+
+ Sykmasine kieze
+
+
+ Standert-sykmasinen opnij ynstelle
+
+
+ + In oare sykmasine tafoegje
+ Sykmasinen fuortsmite
+ Fuortsmite
+
+
+ Noch in sykmasine tafoegje
+
+ Selektearje jo winske sykmasine:
+
+
+ Sykmasine tafoegje
+
+ Namme sykmasine
+ Te brûken sykterm
+ Bewarje
+
+
+ Bygelyls: example.com/search/?q=%s
+
+ Nije sykmasine tafoege.
+
+ Fier namme fan sykmasine yn
+ Dizze namme wurdt al troch in ynstallearre sykmasine brûkt.
+
+ Fier syksterm yn
+
+ Kontrolearje oft sykterm oan foarbyldnotaasje foldocht
+
+
+ Ynfier wiskje
+
+
+ Fuortsmite
+
+
+ Navigaasjeskiednis wiskje
+
+
+ Iepen ljepblêden: %1$s
+
+
+ Befeilige ferbining
+
+
+ Lade
+
+
+ Website laden
+
+
+ Mear opsjes
+
+
+ Knop foar mear opsjes
+
+
+ Foarút blêdzje
+
+
+ Website opnij lade
+
+
+ Werom blêdzje
+
+
+ Laden fan website stopje
+
+
+ Tebek nei foarige app
+
+
+ Oantal blokkearre trackers
+
+
+ Trackers blokkearje
+
+ Jo rjochten
+
+ Keppeling yn oare app iepenje
+
+ Jo kinne %1$s ferlitte om dizze keppeling yn %2$s te iepenjen.
+
+ Fyn in app dy’t dizze keppeling iepenje kin
+
+ Gjin inkelde app op jo apparaat kin dizze keppeling iepenje. Jo kinne %1$s ferlitte en %2$s iepenje om in geskikte app te finen.
+
+ Priveenavigaasje ôfslute?
+
+
+ %1$s is foltôge
+
+
+ Iepenje
+
+
+
+
+
+
+
+
+
+
+ Tafoege oan fluchkeppelingen!
+
+ Server net fûn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Slute
+
+
+
+ Wolkom bij %1$s
+
+
+ Fluch. Privee. Gjin ôflieding.
+
+
+ Begjinne
+
+
+
+ %1$s is net lykas oare browsers
+
+
+
+ As jo de app slute wiskje wy jo skiednis foar ekstra privacy.
+
+
+
+ Meitsje %1$s jo standert om jo gegevens te beskermjen mei elke keppeling dy’t jo iepenje.
+
+
+ As standertbrowser ynstelle
+
+
+ Oerslaan
+
+
+
+ Fergrutsje jo privacy
+
+ Bring priveenavigaasje nei in heger nivo. Blokkearje advertinsjes en oare ynhâld dy’t jo op websites folgje kinne en laadtiden fan siden ferlingje.
+
+
+ Sykje op jo manier
+
+ Op syk nei wat oars? Kies in oare standertsykmasine yn Ynstellingen.
+
+
+ Foegje keppelingen ta oan jo startskerm
+
+ Kear fluch werom nei jo favorite websites yn %1$s. Selektearje gewoanwei ‘Tafoegje oan startskerm’ fan it %1$s-menu út.
+
+
+ Meitsje privacy gewoan
+
+ Stel %1$s yn as jo standertbrowser en ûnderfyn de foardielen fan priveenavigaasje as jo websiden iepenje fan oare apps út.
+
+ OK, begrepen!
+ Oerslaan
+ Folgjende
+
+
+ -
+
+
+ Tafoegje
+
+
+ JA
+
+
+ Annulearje
+
+
+ NEE
+
+
+ Fluchkeppeling iepenet mei Ferbettere beskerming tsjin folgjen útskeakele
+
+
+ Priveenavigaasjesesje
+
+
+ Mei notifikaasjes kinne jo fia ien tik jo %1$s-sesje wiskje. Jo hoege de app net te iepenjen of te sjen wat der yn jo browser aktyf is.
+
+
+ Navigaasjeskiednis wiskje
+
+
+ Firefox downloade
+
+
+
+
+
+
+
+
+ Mozilla Public License en oare opensourcelisinsjes.]]>
+
+
+ hjir te finen.]]>
+
+
+ lisinsjes.]]>
+
+
+ GNU General Public License v3 en dy hjir beskikber binne.]]>
+
+
+ Brûkersnamme
+ Wachtwurd
+ Wiskje
+
+
+
+ Befeilige ferbining
+ Net-befeilige ferbining
+
+ Ferifiearre troch: %1$s
+
+
+ Sidefeilichheid
+ URL bestiet al
+
+
+ Sykje op side
+
+
+ Sykje op side
+
+
+ %1$d/%2$d
+
+ %1$d fan %2$d
+
+
+ Folgjende resultaat fine
+
+ Foarige resultaat fine
+
+ Sykje op side slute
+
+
+
+
+ Desktopwebsite opfreegje
+
+
+ Desktopwebsite
+
+
+ URL kopiearre
+
+
+ Untwikkelersark
+
+
+ Keppelingen yn apps iepenje
+
+
+ Avansearre
+
+
+ Websitemachtigingen
+
+
+ Reduksje fan cookiebanners
+
+
+ Oan
+
+
+ Ut
+
+
+ Reduksje fan cookiebanners
+
+
+ Sjoch minder banners troch, wannear mooglik, cookiefersiken automatysk te wegerjen.
+
+ -->
+ Reduksje fan cookiebanners
+
+
+ OAN foar dizze website
+
+
+ Website wurdt op dit stuit net stipe
+
+
+ ÚT foar dizze website
+
+
+ Reduksje fan cookiebanners
+
+
+ ÚT foar dizze website
+
+
+ OAN foar dizze website
+
+
+ Reduksje fan cookiebanners ynskeakelje foar %1$s?
+
+
+ Reduksje fan cookiebanners útskeakelje foar %1$s?
+
+
+ %1$s wisket de cookies foar dizze website en fernijt de side. As alle cookies wiske wurde, wurde jo mooglik ôfmeld of wurde winkelweintsjes lege.
+
+
+ %1$s kin probearje om cookiefersiken automatysk te wegerjen.
+
+
+ Dizze website wurdt op it stuit net stipe troch Reduksje fan cookiebanners. Wolle jo ús team freegje om dizze website te beoardielen en yn de takomst stipe ta te foegjen?
+
+
+ Annulearje
+
+
+ Stipe oanfreegje
+
+
+ Fersyk om te websitestipe yntsjinne.
+
+
+ Fersyk om te websitestipe yntsjinne.
+
+
+
+ %1$s probearret cookiefersiken ôf te wizen om ferfelende cookiebanners fuort te heljen.\n\nBehear foarkarren foar cookiebanners yn %2$s.
+
+
+ ynstellingen
+
+
+ Automatysk ôfspylje
+
+
+ Tastean:
+
+
+ 1. Gean nei Android-ynstellingen
+
+
+ Machtigingen]]>
+
+
+ Nei Ynstellingen
+
+
+ %1$s op ‘AAN’]]>
+
+
+ Kamera
+
+
+ Mikrofoan
+
+
+ Lokaasje
+
+
+ Melding
+
+
+ DRM-behearde ynhâld
+
+
+ Freegje om tastimming
+
+
+ Blokkearre
+
+
+ Tastien
+
+
+ Blokkearre troch Android
+
+
+ Audio en fideo tastean
+
+
+ Allinnich audio blokkearje
+
+
+ Oanrekommandearre
+
+
+ Audio en fideo blokkearje
+
+
+ Undersiken
+
+
+ Firefox kin sa no en dan ûndersiken ynstallearje en útfiere.
+
+
+ Mear ynfo
+
+
+ De tapassing wurdt ôfsluten om wizigingen ta te passen
+
+
+ Fuortsmite
+
+
+ Aktyf
+
+
+ Foltôge
+
+
+ Remote debugging fia USB/wifi
+
+
+ Untskoattelje
+
+
+ Befêstigje it gebrûk fan jo fingerôfdruk
+
+
+ Jo kinne jo fingerôfdruk brûke om mei jo aktuele app-sesje troch te gean.
+
+
+ Keppeling iepenje yn nije sesje
+
+
+ Fingerôfdrukpiktogram
+
+
+ Fingerôfdruk net werkend. Probearje it opnij.
+
+
+ Finger te fluch beweegd. Probearje it opnij.
+
+
+ Syksuggestjes toane?
+
+
+ Om suggestjes te krijen, moat %1$s wat jo yn de adresbalke yntype nei de keazen sykmasine stjoere.
+
+
+ Nee
+
+
+ Ja
+
+
+ Guon sykmasinen kinne gjin suggestjes toane.
+
+
+ Fuortsmite
+
+
+
+
+ Wurket in website net goed?\n Probearje Beskerming tsjin folgjen út te skeakeljen
+
+
+ Tafoegje oan startskerm]]>
+
+
+ Alle keppelingen yn %1$s iepenje\n Stel %1$s yn as jo standertbrowser
+
+
+ URL’s foar meastbrûkte websites automatysk oanfolje\n Hâld jo finger op in URL yn de adresbalke
+
+
+ In keppeling yn in nij ljepblêd iepenje\n Hâld jo finger op in keppeling op in side
+
+
+ Tips op startscherm útskeakelje
+
+
+ Nij ljepblêd iepene
+
+
+ Wikselje
+
+
+ Folslein skerm wurdt iepene
+
+
+ Wikselje daliks nei keppeling yn nij ljepblêd
+
+
+ Potinsjeel gefaarlike en misliedende websites blokkearje
+
+ Rapportearre misliedende en oanfals-, malware- en net-winske-software-websites blokkearje.
+
+
+ Allinnich-HTTPS-modus
+
+
+ Probearret foar in bettere befeiliging automatysk mei it HTTPS-fersiferingsprotokol ferbining te meitsjen mei websites.
+
+
+ Utsûnderingen
+
+ Jo hawwe ynhâldsblokkearring foar dizze websites útskeakele.
+
+ Fuortsmite
+
+ Alle websites fuortsmite
+
+
+ Cookies blokkearje
+
+
+ Wolle jo cookies blokkearje?
+
+
+ Ljepblêd ferûngelokke
+
+ Sorry, wy hawwe in probleem mei dit ljepblêd.
+
+ As priveebrowser bewarje wy dit ljepblêd net en kinne it net werstelle.
+
+ Ljepblêd slute
+
+
+
+
+
+ Ungelokrapport nei Mozilla ferstjoere
+
+
+
+
+ Trackers blokkearre sûnt %s
+
+ Ynhâld
+
+ Advertinsjes
+
+ Sosjaal
+
+ Analyze
+
+ Ferbettere beskerming tsjin folgjen
+
+ Beskermingen foar dizze website stean ÚT
+
+ Beskermingen foar dizze website stean OAN
+
+ Ferbining is befeilige
+
+ Ferbining is net befeilige
+
+ Te blokkearjen trackers en scripts
+
+
+ Tebek
+
+
+
+ Fuortsmite
+
+
+ Omneame
+
+ Omneame
+
+
+ Namme fluchkeppeling
+
+
+ Bewarre en dielde ôfbyldingen <b>wurde net</b> fuortsmiten as jo skiednis fan %1$s wiskje
+
+
+
+ Tema
+
+ Ljocht
+
+ Donker
+
+ Ynsteld troch Batterijbesparring
+
+ Apparaattema folgje
+
+
+
+ Dizze website stipet gjin HTTPS
+
+
+ Mear ynfo
+Wizigje dizze ynstelling yn Ynstellingen > Privacy en befeiliging > Befeiliging.]]>
+
+
+ Ferbining net befeilige
+
+
+
+As jo earder al mei súkses ferbining hân hawwe mei dizze server, kin de flater mooglik tydlik wêze.
+ ]]>
+
+
+ Ien kin probearje de website nei te dwaan en trochgean kin risikofol wêze.
+
+ %1$s fertrout %2$s net, omdat de sertifikaatútjouwer ûnbekend is, it sertifikaat selsûndertekene is, of de server net de krekte yntermediêre sertifikaten ferstjoert.
+ ]]>
+
+
+
+ Ljepblêd slute
+
+
+
+ Hebbes! Wy hawwe dizze website stoppe om jo te bespionearjen. Tik op elk momint op it skyld om te sjen wat wy blokkearje.
+
+
+ Pop-up slute
+
+
+
+ Jo binne beskerme!
+
+ Dizze standertynstellingen biede in sterke beskerming. Mar it is ienfâldich om de ynstellingen oan jo spesifike behoeften oan te passen.
+
+ Slute
+
+
+ Tik hjir om alles fuort te smiten – skiednis, cookies, alles – en in farske start te meitsjen op in nij ljepblêd.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Slute
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Syk-widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Navigaasjeskiednis wiske! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start jo priveenavigaasjesesje en wy blokkearje trackers en oare minne dingen wylst jo dwaande binne.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Wy litte jo trochgean mei priveenavigaasje, mar krij in folgjende kear in fluggere start mei de %1$s-widget op jo startskerm.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Widget oan startskerm tafoegje
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget oan startskerm tafoege
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ga-rIE/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ga-rIE/strings.xml
new file mode 100644
index 0000000000..a579b81431
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ga-rIE/strings.xml
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+
+ Cealaigh
+ OK
+
+ Sábháil
+
+ Cuardach nó seoladh Gréasáin
+
+ Brabhsáil phríobháideach, go huathoibríoch.\nBrabhsáil. Bánaigh. Arís is arís eile.
+
+ Bánaíodh do stair bhrabhsála.
+
+ Cuardaigh %1$s
+
+ Comhroinn…
+
+ Tuairiscigh Fadhb leis an Suíomh
+
+ Oscail in %1$s
+
+ Oscail in…
+
+ Cuir leis an scáileán Baile
+
+ Socruithe
+ Maidir leis
+ Cabhair
+ Cearta an úsáideora
+
+ Lorgairí blocáilte
+
+ Á chumhachtú ag %1$s
+
+ Comhroinn trí
+
+ Glan an stair bhrabhsála
+
+ Oscail
+
+ Scrios agus Oscail
+
+ Glan
+
+ Glan an stair bhrabhsála
+
+
+ Príobháideachas agus Slándáil
+
+ Lorgaireacht, fianáin, roghanna sonraí
+
+ Réamhshocrú, uathchríochnú
+
+ Maidir le %1$s, cabhair
+
+ Inneachar Gréasáin
+
+ Athrú Aipeanna
+
+ Ginearálta
+
+ Bailiúchán agus Úsáid Sonraí
+
+ Cuardach
+
+ Réamhshocrú
+
+ Ann
+
+ As
+
+ Uathchríochnú URL
+
+ + Cuir URL saincheaptha leis
+
+ Cuir URL saincheaptha leis
+
+ Cuir URL saincheaptha leis
+
+ Fianáin agus Sonraí Suímh
+
+ Roghanna Sonraí
+
+ Bain URLanna saincheaptha
+
+ Tuilleadh eolais
+
+ URL le cur leis
+
+ Greamaigh nó clóscríobh URL
+
+ Sampla: mozilla.org
+
+ Sampla: example.com
+
+ Bain
+
+ Bain
+
+ Deimhnigh an URL a chuir tú isteach arís.
+
+ Teanga
+ Réamhshocrú an chórais
+
+ Príobháideachas
+ Cuir cosc ar lorgairí fógraíochta
+ Cuir cosc ar lorgairí anailísíochta
+ Cuir cosc ar lorgairí sóisialta
+ Cuir cosc ar lorgairí ábhair eile
+ Cuir cosc ar fhianáin
+
+ Mód Rúnda
+ Folaigh leathanaigh Ghréasáin agus an aip á hathrú
+
+ Feidhmíocht
+ Cuir cosc ar chlónna Gréasáin
+ D\'fhéadfadh go mbeadh deilbhíní nó íomhánna ar iarraidh
+
+ Cuir cosc ar JavaScript
+
+ Mozilla
+ Seol sonraí úsáide chugainn
+
+ Tuilleadh eolais
+
+ Fógra Príobháideachais
+
+ Maidir le %1$s
+
+ Innill chuardaigh suiteáilte
+
+ Athchóirigh na hinnill chuardaigh réamhshocraithe
+
+ + Cuir inneall cuardaigh leis
+ Bain innill chuardaigh
+ Bain
+
+ Inneall cuardaigh nua
+
+ Ainm an innill cuardaigh
+ Teaghrán cuardaigh
+ Sábháil
+
+ Sampla: example.com/search/?q=%s
+
+ Cuireadh inneall cuardaigh nua leis.
+
+ Cuir isteach ainm an innill cuardaigh
+
+ Cuir teaghrán cuardaigh isteach
+
+ Glan an réimse
+
+ Díbir
+
+ Glan an stair bhrabhsála
+
+ Cluaisíní oscailte: %1$s
+
+ Ceangal slán
+
+ Á lódáil
+
+ Luchtaíodh an suíomh Gréasáin
+
+ Tuilleadh roghanna
+
+ Téigh ar aghaidh
+
+ Athlódáil an suíomh
+
+ Téigh ar gcúl
+
+ Stop ag lódáil an tsuímh
+
+ Fill ar an aip roimhe seo
+
+ Cuir cosc ar lorgairí
+
+ Cearta an úsáideora
+
+ Oscail an nasc in aip eile
+ Is féidir leat %1$s a fhágáil chun an nasc seo a oscailt in %2$s.
+ Aimsigh aip lenar féidir an nasc seo a oscailt
+ Níl aon aip ar do ghléas atá in ann an nasc seo a oscailt. Is féidir leat %1$s a fhágáil chun aip oiriúnach a lorg in %2$s.
+ Scoir de Bhrabhsáil Phríobháideach?
+
+ %1$s críochnaithe
+
+ Oscail
+
+
+
+
+
+
+
+
+
+ Níor aimsíodh an freastalaí
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Príobháideachas praiticiúil proifisiúnta
+
+ Cuir aicearraí leis an leathanach baile
+
+ OK, tuigim!
+ Ná bac leis
+ Ar Aghaidh
+
+ -
+
+ Cuir Leis
+
+ Cealaigh
+
+ Íoslódáil Firefox
+
+ Ainm úsáideora
+ Focal Faire
+ Glan
+
+ Ceangal Slán
+ Ceangal Neamhshlán
+ Fíoraithe ag: %1$s
+
+ Slándáil an tSuímh
+ Tá an URL ann cheana
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-gl/strings.xml b/mobile/android/focus-android/app/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000000..1008df7889
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-gl/strings.xml
@@ -0,0 +1,1098 @@
+
+
+
+
+
+
+
+
+ Cancelar
+
+ Aceptar
+
+ Gardar
+
+
+ Buscar ou escribir o enderezo
+
+ Navegación privada automática.\nNavega. Borra. Repite.
+
+
+ Borrouse o seu historial de navegación.
+
+ Borrouse o historial de navegación
+
+
+ Borrouse o historial de navegación da lapela.
+
+
+ Buscar %1$s
+
+
+ Compartir…
+
+
+ Incidencia neste sitio
+
+
+ Abrir no %1$s
+
+
+ Abrir en…
+
+
+ Engadir á páxina inicial
+
+
+ Engadir aos atallos
+
+ Retirar dos atallos
+
+
+ Axustes
+ Sobre
+ Axuda
+ Os seus dereitos
+
+
+ Rastrexadores bloqueados
+
+
+ Desactivar esta opción pode solucionar algúns problemas do sitio
+
+
+ Bloqueo de contido
+
+ Desactive para corrixir algúns sitios
+
+
+ Co apoio de %1$s
+
+
+ Compartir por
+
+ Quere borrar o historial de navegación?
+ Tocar ou borrar esta notificación para borrar de forma segura o seu historial de navegación.
+
+
+ Tocar ou deslizar esta notificación para borrar de forma segura o seu historial de navegación.
+
+ Borrar o historial de navegación
+
+
+ Abrir
+
+
+ Borrar e abrir
+
+
+ Borrar
+
+
+ Borrar o historial de navegación
+
+
+
+ Borrar e abrir
+
+
+ Borrar e abrir %1$s
+
+
+ Busca en Focus
+
+ Buscar en Klar
+
+ Buscar en Focus Beta
+
+ Buscar en Focus Nightly
+
+
+ %1$s dálle o control.
+Úseo como navegador privado:
+
+ Buscar e navegar directamente na aplicación
+ Bloquear rastrexadores (ou actualizar a configuración para permitir rastrexadores)
+ Borrar para eliminar as cookies, así como o historial de busca e navegación
+
+
+%1$s é producido por Mozilla. A nosa misión é fomentar unha Internet saudable e aberta.
+Máis información
]]>
+
+
+ Privacidade e seguranza
+
+
+ Seguimento, cookies, datos recollidos
+
+
+ Estabelecer como predeterminado, completado automático
+
+
+
+
+ Sobre o %1$s, axuda
+
+
+ Protección mellorada contra o rastrexo
+
+
+ Contido web
+
+
+ Cambiar entre aplicativos
+
+
+ Xeral
+
+
+ Navegador predeterminado, idioma
+
+
+ Uso e recollida de datos
+
+ Buscar
+
+
+ Obter suxestións de busca
+
+ %1$s enviará o que escribiu na barra de enderezos ao buscador
+
+
+ Predeterminado
+
+
+ Buscador
+
+
+ Activado
+
+
+ Desactivado
+
+
+ Completado automático do URL
+
+
+ Para os sitios máis populares
+
+
+ Actíveo para que %s complete automaticamente máis de 450 URL populares na barra de enderezos.
+
+
+ Para os sitios que engada
+
+
+ Activar para que %s complete automaticamente os seus URL favoritos.
+
+
+ Xestionar os sitios
+
+
+ Xestionar os sitios
+
+
+ + Engadir URL personalizado
+
+
+ A súa lista para completar automaticamente:
+
+
+ Engadir o URL
+
+
+ Engadir URL personalizado
+
+
+ Engadir URL personalizado
+
+
+ Engadir a ligazón para completar automaticamente
+
+
+ Cookies e datos do sitio
+
+
+ Datos recollidos
+
+
+ Retirar URL personalizados
+
+
+ Máis información
+
+
+ Engadir e xestionar o completado automático personalizado dos URL.
+
+
+ URL a engadir
+
+
+ Pegar ou escribir URL
+
+
+ Exemplo: mozilla.org
+
+
+ Exemplo: example.com
+
+
+ Engadiuse o URL personalizado.
+
+
+ Retirar
+
+
+ Retirar
+
+
+ Revise o URL que escribiu.
+
+ Idioma
+
+ Predeterminado do sistema
+
+ Privacidade
+ Bloquear os elementos de seguimento de publicidade
+ Algún anuncios fan o seguimento das súas visitas as páxinas aínda que que non acceda a eles
+ Bloquear os elementos de seguimento analíticos
+ "Úsanse para recoller, analizar e medir accións como premer na pantalla ou desprazarse pola páxina "
+ Bloquear os elementos de seguimento social
+ Incrustados nos sitios para facer o seguimento das súas visitas e para amosar algúns elementos como os botóns de compartir
+ Bloquear os outros elementos de seguimento de contido
+ A activación pode causar que algunhas páxinas se comporten de forma inesperada
+ Bloquear as cookies
+
+
+ Non, grazas
+ Bloquear só as cookies de seguimento de terceiras partes
+ Só bloquea as cookies de terceiros
+
+ Bloquear as cookies entre sitios
+ Si, por favor
+
+
+ Usar a pegada dixital para desbloquear o aplicativo
+
+
+ Desbloquee coa pegada dixital se engadiu atallos ou cando xa está aberto un sitio web en %s.
+
+
+ Invisible
+
+ Agocha as páxinas web cando cambia de aplicativos e evita capturas de pantalla.
+
+ Seguranza
+
+ Rendemento
+ Bloquear tipos de letra web
+
+ Pode facer que non vexan algunhas iconas ou imaxes
+
+ Bloquear JavaScript
+
+ As páxinas poden cargar máis rápido, pero tamén poden comportarse de forma inesperada
+
+
+ Definir %1$s como navegador predeterminado
+
+ Mozilla
+ Enviar datos de uso
+
+
+ Máis información
+
+
+ Mozilla esforzase por recoller só os datos necesarios para fornecer e mellorar %1$s para todos.
+
+
+ Política de privacidade
+
+
+ Información de licenciamento
+
+
+ Bibliotecas que empregamos
+
+
+ %s | Bibliotecas OSS
+
+
+ Sobre o %1$s
+
+
+ Buscadores instalados
+
+
+ Escolla un buscador
+
+
+ Restaurar os buscadores predeterminados
+
+
+ + Engadir outro buscador
+ Retirar buscadores
+ Retirar
+
+
+ Engadir outro buscador
+
+ Seleccione o seu buscador preferido:
+
+
+ Engadir buscador
+
+ Nome do buscador
+ Cadea de busca que se usará
+ Gardar
+
+
+ Exemplo: example.com/search/?q=%s
+
+ Engadiuse un novo buscador.
+
+ Escriba o nome do buscador
+ Xa hai un buscador instalado con ese nome.
+
+ Escriba a cadea de busca
+
+ Comprobe que a cadea de busca coincide co formato do exemplo
+
+
+ Borrar entrada
+
+
+ Rexeitar
+
+
+ Borrar o historial de navegación
+
+
+ Lapelas abertas: %1$s
+
+
+ Conexión segura
+
+
+ Cargando
+
+
+ Cargouse o sitio web
+
+
+ Máis opcións
+
+
+ Botón de máis opcións
+
+
+ Avanzar no historial
+
+
+ Recargar sitio web
+
+
+ Retroceder no historial
+
+
+ Deter a carga do sitio web
+
+
+ Volver ao aplicativo anterior
+
+
+ Número de rastrexadores bloqueados
+
+
+ Bloquear os elementos de seguimento
+
+ Os seus dereitos
+
+ Abrir a ligazón noutro aplicativo
+
+ Pode saír do %1$s para abrir esta ligazón co %2$s.
+
+ Atopa un aplicativo que pode abrir a ligazón
+
+ Ningún dos aplicativos no seu dispositivo pode abrir esta ligazón. Pode saír do %1$s para buscar en %2$s un aplicativo compatíbel.
+
+ Quere saír da navegación privada?
+
+
+ %1$s rematou
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+
+ Engadido aos atallos!
+
+ Non se atopou o servidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pechar
+
+
+
+ Reciba a benvida a %1$s
+
+
+ Rápido. Privado. Sen distraccións.
+
+
+ Comezar
+
+
+
+ %1$s non é como outros navegadores
+
+
+ Borramos o seu historial cando pecha a aplicación para obter máis privacidade.
+
+
+
+ Establece %1$s como predeterminado para protexer os seus datos con cada ligazón que abra.
+
+
+ Estabelecer como navegador predeterminado
+
+
+ Saltar
+
+
+ Potencia a súa privacidade
+
+ A navegación privada pasa a un nivel superior. Bloquea anuncios e outro contido que pode facerlle un seguimento a través dos sitios e atrasar a carga das páxinas.
+
+
+ As súas buscas, ao seu modo
+
+ Busca algo diferente? Escolla outra buscador predeterminado nos Axustes.
+
+
+ Engada atallos a súa páxina inicial
+
+ Volva máis rápido aos seus sitios favoritos no %1$s. Seleccione «Engadir á páxina inicial» no menú do %1$s.
+
+
+ Converte a privacidade nun hábito
+
+ Estabeleza %1$s como navegador predeterminado e experimente os beneficios da navegación privada ao abrir páxinas web dende outros aplicativos.
+
+ Entendido!
+ Saltar
+ Seguinte
+
+
+ -
+
+
+ Engadir
+
+
+ SI
+
+
+ Cancelar
+
+
+ NON
+
+
+ O atallo abrirase coa protección de seguimento mellorada desactivada
+
+
+ Sesión de navegación privada
+
+
+ As notificacións permítenlle borrar a sesión de %1$s cun toque. Non necesita abrir o aplicativo nin ver o que se está executando no navegador.
+
+
+ Borrar o historial de navegación
+
+
+ Descargar o Firefox
+
+
+
+
+
+
+
+
+ Licenza pública de Mozilla e outras licenzas de código aberto.]]>
+
+
+ aquí.]]>
+
+
+ licenzas gratuítas e de código aberto.]]>
+
+
+ GNU General Public License v3, e dispoñible aquí .]]>
+
+
+ Nome de usuario
+ Contrasinal
+ Borrar
+
+
+
+ Conexión segura
+ Conexión non segura
+
+ Verificado por: %1$s
+
+
+ Seguranza do sitio
+ Xa existe o URL
+
+
+ Atopar na páxina
+
+
+ Atopar na páxina
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Atopar o seguinte resultado
+
+ Atopar o resultado anterior
+
+ Pechar a busca
+
+
+
+
+ Solicitar sitio de escritorio
+
+
+ Sitio de escritorio
+
+
+ Copiouse o URL
+
+
+ Ferramentas de desenvolvemento
+
+
+ Abrir ligazóns con aplicacións
+
+
+ Avanzados
+
+
+ Permisos do sitio
+
+
+ Redución do aviso de cookies
+
+
+ Activado
+
+
+ Desactivado
+
+
+ Redución do aviso de cookies
+
+
+ Vexa menos avisos ao rexeitar automaticamente as solicitudes de cookies, cando sexa posible.
+
+ -->
+ Redución do aviso de cookies
+
+
+ Activado para este sitio
+
+
+ Sitio actualmente non soportado
+
+
+ Desactivado para este sitio
+
+
+ Redución do aviso de cookies
+
+
+ Desactivado para este sitio
+
+
+ Activado para este sitio
+
+
+ Quere activar a redución do aviso de cookies para %1$s?
+
+
+ Quere desactivar a redución do aviso de cookies para %1$s?
+
+
+ %1$s borrará as cookies deste sitio e actualizará a páxina. Ao borrar todas as cookies, é posible que peche a sesión ou baleire os carriños da compra.
+
+
+ %1$s pode tentar rexeitar automaticamente as solicitudes de cookies.
+
+
+ Este sitio actualmente non é compatible coa redución de avisos de cookies. Quere solicitar ao noso equipo que revise este sitio web e que engada asistencia no futuro?
+
+
+ Cancelar
+
+
+ Pedir soporte
+
+
+ A solicitude de soporte enviouse.
+
+
+ A solicitude de soporte enviouse.
+
+
+
+ %1$s tenta rexeitar as solicitudes de cookies para rexeitar os molestos avisos de cookies.\n\nXestions as preferencias dos avisos de cookies en %2$s.
+
+ configuración
+
+
+ Reprodución automática
+
+
+ Para permitilo:
+
+
+ 1. Vaia á Configuración de Android
+
+
+ Permisos]]>
+
+
+ Ir á configuración
+
+
+ %1$s a ACTIVADO]]>
+
+
+ Cámara
+
+
+ Micrófono
+
+
+ Localización
+
+
+ Notificación
+
+
+ Contido controlado por DRM
+
+
+ Preguntar antes de permitir
+
+
+ Bloqueado
+
+
+ Permitido
+
+
+ Bloqueado por Android
+
+
+ Permitir son e vídeo
+
+
+ Bloquear só o son
+
+
+ Recomendado
+
+
+ Bloquear son e vídeo
+
+
+ Estudos
+
+
+ Firefox pode instalar e executar estudos de vez en cando.
+
+
+ Máis información
+
+
+ A aplicación pecharase para aplicar os cambios
+
+
+ Retirar
+
+
+ Activo
+
+
+ Rematado
+
+
+ Depuración remota por USB/Wi-Fi
+
+
+ Desbloquear
+
+
+ Confirmar usando a súa pegada dixital
+
+
+ Pode usar a túa pegada dixital para continuar coa sesión actual da aplicación.
+
+
+ Abrir a ligazón nunha nova sesión
+
+
+ Icona da pegada dixital
+
+
+ Non se recoñece a pegada dixital. Tente de novo.
+
+
+ Moveu demasiado rápido o dedo. Tente de novo.
+
+
+ Amosar suxestións de busca?
+
+
+ Para obter suxestións, %1$s debe enviar o que escribe na barra de enderezos ao motor de busca.
+
+
+ Non
+
+
+ Si
+
+
+ É posíbel que algúns buscadores non amosen suxestións.
+
+
+ Rexeitar
+
+
+
+
+ O sitio comportase de forma inesperada?\n Proba a desactivar a protección contra o seguimento
+
+
+ Engadir á páxina inicial]]>
+
+
+ Abra todas as ligazóns no %1$s\n Estableza %1$s como o navegador predeterminado
+
+
+ Completar automaticamente os URL máis visitados\n Unha pulsación longa en calquera URL na barra de enderezos
+
+
+ Abrir unha ligazón nunha nova lapela\n Unha pulsación longa en calquera ligazón nunha páxina
+
+
+ Desactivar os consellos na pantalla de inicio
+
+
+ Abriuse unha nova lapela
+
+
+ Trocar
+
+
+ Entrado no modo de pantalla completa
+
+
+ Cambiar a unha ligazón nunha nova lapela inmediatamente
+
+
+ Bloquear sitios potencialmente perigosos e enganosos
+
+ Bloquear sitios notificados como enganosos e de ataque, sitios de software malicioso e sitios de software non desexado.
+
+ Modo só HTTPS
+
+
+ Tenta conectarse automaticamente a sitios mediante o protocolo de cifrado HTTPS para aumentar a seguridade.
+
+
+ Excepcións
+
+ Desactivou o bloqueo de contido para estes sitios web.
+
+ Retirar
+
+ Retirar todos os sitios web
+
+
+ Bloquear as cookies
+
+
+ Quere bloquear as cookies?
+
+
+ A lapela quebrou
+
+ Sentímolo. Temos un problema con esta lapela.
+
+ Como navegador privado, nunca gardamos e non podemos restaurar esta lapela.
+
+ Pechar lapela
+
+
+ Enviar informe de quebra a Mozilla
+
+
+
+
+ Rastrexadores bloqueados desde %s
+
+ Contido
+
+ Publicidade
+
+ Social
+
+ Analítica
+
+ Protección mellorada contra o rastrexo
+
+ As proteccións están DESACTIVADAS neste sitio
+
+ As proteccións están ACTIVADAS neste sitio
+
+ A conexión é segura
+
+ A conexión non é segura
+
+ Rastrexadores e scripts para bloquear
+
+
+ Retroceder
+
+
+
+ Retirar
+
+ Renomear
+
+
+ Renomear
+
+ Nome do atallo
+
+
+ As imaxes gardadas e compartidas <b>non se</b> eliminarán cando borre o historial de %1$s
+
+
+
+ Tema
+
+ Claro
+
+ Escuro
+
+ Configurado polo aforro de batería
+
+ Segundo o tema do dispositivo
+
+
+ Este sitio non admite HTTPS
+
+
+ Máis información
+ Cambie esta configuración en Configuración > Privacidade e & Seguridade > Seguridade.]]>
+
+
+ A conexión non é segura
+
+
+
+ Se se conectou a este servidor correctamente no pasado, o erro pode ser temporal.
+ ]]>
+
+
+ Alguén podería tentar suplantar a identidade do sitio e continuar pode ser arriscado.
+
+ %1$s non confía en %2$s porque o emisor do seu certificado é descoñecido, o certificado está asinado por si mesmo ou o servidor non está a enviar os certificados intermedios correctos.
+ ]]>
+
+
+
+ Pechar a lapela
+
+
+
+ Excelente! Evitamos que este sito web o espiara. Toque o escudo para ver o que estamos a bloquear.
+
+
+ Pecha a ventá emerxente
+
+
+
+ Estás protexido!
+
+ Esta configuración predeterminada ofrece unha forte protección. Pero é doado axustar a configuración para satisfacer as túas necesidades específicas.
+
+ Rexeitar
+
+
+ Toque aquí para eliminalo todo — historial, cookies, todo — e comezar de novo nunha nova lapela.
+
+
+
+
+ Pechar
+
+
+ Widget de busca
+
+
+ Borrouse o historial de navegación. 🎉
+
+
+ Comece a súa sesión de navegación privada e bloquearemos os rastrexadores e outras ameazas mentres navega.
+
+
+ Deixarémolo coa súa navegación privada, pero a próxima vez comezará máis rápido co widget %1$s na súa pantalla de inicio.
+
+
+ Engadir widget á pantalla de inicio
+
+
+ Engadiuse o widget á pantalla de inicio
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-gu-rIN/strings.xml b/mobile/android/focus-android/app/src/main/res/values-gu-rIN/strings.xml
new file mode 100644
index 0000000000..ab21f8c9eb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-gu-rIN/strings.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+ રદ કરો
+ OK
+
+ સાચવો
+
+ શોધો અથવા સરનામું દાખલ કરો
+
+ આપમેળે ખાનગી બ્રાઉઝિંગ.\nબ્રાઉઝ કરો. કાઢો. પુનરાવર્તન કરો.
+
+ તમારો બ્રાઉઝિંગ ઇતિહાસ કાઢી નાખવામાં આવ્યો છે.
+
+ ટૅબનો બ્રાઉઝિંગ ઇતિહાસ કાઢી નાખવામાં આવ્યો છે.
+
+ %1$s ની શોધ માં
+
+ વહેંચો…
+
+ સાઇટ સમસ્યાનો અહેવાલ આપો
+
+ %1$s માં ખોલો
+
+ માં ખોલો…
+
+ મુખ્ય સ્ક્રીન પર ઉમેરો
+
+ સેટિંગ્સ
+ વિશે
+ મદદ
+ તમારા અધિકારો
+
+ ટ્રેકર્સ અવરોધિત
+
+ આને બંધ કરવું એ કેટલીક સાઇટ સમસ્યાઓને ઠીક કરી શકે છે
+
+ સામગ્રી અવરોધિત
+ કેટલીક સાઇટ્સને ઠીક કરવા બંધ કરો
+
+ %1$s દ્વારા સંચાલિત
+
+ આની મારફતે વહેંચો
+
+ બ્રાઉઝિંગ ઇતિહાસને કાઢી નાખો
+
+ ખોલો
+
+ કાઢી નાખો અને ખોલો
+
+ કાઢી નાખો
+
+ બ્રાઉઝિંગ ઇતિહાસને કાઢી નાખો
+
+
+ કાઢી નાખો અને ખોલો
+
+ કાઢી નાખો અને %1$s ને ખોલો
+
+ ગોપનીયતા અને સુરક્ષા
+
+ ટ્રેકિંગ, કૂકીઝ, માહિતી પસંદગીઓ
+
+ મૂળભૂત સેટ કરો, સ્વતઃપૂર્ણ
+
+ %1$s વિશે, સહાય
+
+ વેબ સામગ્રી
+
+ એપ્લિકેશન બદલો
+
+ સામાન્ય
+
+ માહિતીનો સંગ્રહ અને ઉપયોગ
+
+ શોધો
+
+ શોધ સૂચનો મેળવો
+ %1$s તમે જે સરનામા બારમાં લખો છો તે તમારા શોધ એન્જિનમાં મોકલશે
+
+ મૂળભૂત
+
+ શોધ એન્જિન
+
+ ચાલુ
+
+ બંધ
+
+ URL સ્વતઃપૂર્ણ
+
+ ટોચની સાઇટ્સ માટે
+
+ તમે ઉમેરો છો તે સાઇટ્સ માટે
+
+ સાઇટ્સની ગોઠવણ કરો
+
+ સાઇટ્સની ગોઠવણ કરો
+
+ + પોતાનું URL ઉમેરો
+
+ પોતાનું URL ઉમેરો
+
+ પોતાનું URL ઉમેરો
+
+ સ્વતઃપૂર્ણ કરવા માટે લિંક ઉમેરો
+
+ કૂકીઝ અને સાઈટની માહિતી
+
+ માહિતી પસંદગીઓ
+
+ પોતાના URLs દૂર કરો
+
+ વધુ શીખો
+
+ પોતાના સ્વતઃપૂર્ણ URLs ને ઉમેરો અને સંચાલિત કરો.
+
+ URL ઉમેરવુ
+
+ પેસ્ટ કરો અથવા URL દાખલ કરો
+
+ ઉદાહરણ: mozilla.org
+
+ ઉદાહરણ: example.com
+
+ નવું કસ્ટમ URL ઉમેર્યું.
+
+ દૂર કરો
+
+ દૂર કરો
+
+ તમે દાખલ કરેલ URL ને બે વાર તપાસો.
+
+ ભાષા
+ સિસ્ટમ મૂળભૂત
+
+ ખાનગીપણું
+ જાહેરાત ટ્રેકર્સને અવરોધિત કરો
+ કેટલાક જાહેરાતો સાઇટની મુલાકાત લે છે, પછી ભલે તમે જાહેરાતો પર ક્લિક ન કરો
+ વિશ્લેષણાત્મક ટ્રેકર્સને અવરોધિત કરો
+ ટેપીંગ અને સ્ક્રોલિંગ જેવી પ્રવૃત્તિઓ એકત્રિત કરવા, પૃથ્થકરણ અને માપવા માટે વપરાય છે
+ સામાજિક ટ્રેકર્સને અવરોધિત કરો
+ સાઇટ્સ પર એમ્બેડ કરેલા તમારી મુલાકાતોને ટ્રૅક કરવા અને શેર કરો બટનો જેવી કાર્યક્ષમતા દર્શાવવા માટે
+ અન્ય સામગ્રી ટ્રેકર્સને અવરોધિત કરો
+ સક્રિય કરવાથી કેટલાક પૃષ્ઠો અનપેક્ષિત રૂપે વર્તન કરી શકે છે
+ કૂકીઝને અવરોધિત કરો
+
+ માત્ર તૃતીય-પક્ષ ટ્રેકર કૂકીઝને અવરોધિત કરો
+ માત્ર 3-પક્ષ કૂકીઝને અવરોધિત કરો
+
+ એપ્લિકેશનને અનલૉક કરવા માટે આંગળીના નીશાનનો ઉપયોગ કરો
+
+ તરકિબ
+ એપ્લિકેશન્સને બદલતી વખતે વેબપૃષ્ઠો છુપાવો અને સ્ક્રીનશોટ લેવાનું અવરોધિત કરો.
+
+ સુરક્ષા
+
+ પ્રદર્શન
+ વેબ ફોન્ટ્સને અવરોધિત કરો
+ ખૂટે ચિહ્નો અથવા છબીઓમાં પરિણામ હોઈ શકે છે
+
+ JavaScript અવરોધ
+ પૃષ્ઠો વધુ ઝડપથી લોડ થઈ શકે છે, પરંતુ અણધારી રૂપે વર્તન પણ કરી શકે છે
+
+ %1$s મૂળભૂત બ્રાઉઝર બનાવો
+
+ Mozilla
+ ઉપયોગ ડેટા મોકલો
+
+ વધુ શીખો
+
+ મોઝિલા માત્ર દરેકને માટે %1$s પ્રદાન અને સુધારવાની જરૂર છે તે એકત્રિત કરવાનો પ્રયત્ન કરે છે.
+
+ ખાનગી સૂચના
+
+ %1$s વિશે
+
+ ઇન્સ્ટોલ કરેલા સર્ચ એન્જિનો
+
+ મૂળભૂત શોધ એંજીન તરીકે પુનઃસંગ્રહો
+
+ + અન્ય શોધ એન્જિન ઉમેરો
+ શોધ એંજીન્સ દૂર કરો
+ દૂર કરો
+
+ શોધ એન્જિન ઉમેરો
+
+ શોધ એન્જિન નામ
+ વાપરવા માટે શોધ શબ્દમાળા
+ સંગ્રહો
+
+ ઉદાહરણ: example.com/search/?q=%s
+
+ નવું શોધ એંજિન ઉમેર્યું.
+
+ શોધ એંજીન નામ દાખલ કરો
+ એક સ્થાપિત શોધ એંજીન પહેલેથી જ તે નામનો ઉપયોગ કરી રહ્યું છે.
+
+ શબ્દમાળા દાખલ કરો
+
+ તે તપાસણી કરો કે શબ્દમાળા ઉદાહરણ ફોર્મેટ સાથે મેળ ખાય છે
+
+ સ્પષ્ટ ઇનપુટ
+
+ રદ કરો
+
+ બ્રાઉઝિંગ ઇતિહાસને કાઢી નાખો
+
+ ટૅબ્સ ખોલો: %1$s
+
+ સુરક્ષિત જોડાણ
+
+ લોડ કરી રહ્યું છે
+
+ વેબસાઇટ લોડ થાય છે
+
+ વધુ વિકલ્પ
+
+ વધુ વિકલ્પો બટન
+
+ આગળ નેવિગેટ કરો
+
+ વેબસાઇટ ફરીથી લોડ કરો
+
+ પાછા નેવિગેટ કરો
+
+ વેબસાઇટ લોડ કરવાનું રોકો
+
+ પાછલા એપ્લિકેશન પર પાછા ફરો
+
+ અવરોધિત ટ્રેકર્સની સંખ્યા
+
+ ટ્રેકર્સને અવરોધિત કરો
+
+ તમારા અધિકારો
+
+ અન્ય એપ્લિકેશનમાં લિંક ખોલો
+ તમે %2$sમાં આ લિંકને ખોલવા માટે %1$s છોડી શકો છો.
+ એક એપ્લિકેશન શોધો જે લિંક ખોલી શકે છે
+ તમારા ઉપકરણ પરની કોઈપણ એપ્લિકેશનો આ લિંક ખોલવા માટે સક્ષમ નથી. તમે એપ્લિકેશન માટે %2$s શોધવા માટે %1$s કરી છોડી શકો છો.
+ ખાનગી બ્રાઉઝિંગ બહાર નીકળીએ?
+
+ %1$s સમાપ્ત થઈ
+
+ ખોલો
+
+
+
+
+
+
+
+
+
+ સર્વર મળ્યું નહિં
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ તમારી ગોપનીયતા વધારો
+ ખાનગી બ્રાઉઝિંગ આગલા સ્તર પર લો. જાહેરાતો અને અન્ય સામગ્રીને અવરોધિત કરો કે જે તમને સાઇટ્સ પર ટ્રૅક કરી શકે છે અને પૃષ્ઠ લોડ વખતને નીચે કરી શકે છે.
+
+ તમારી શોધ, તમારી રીત
+ કંઈક અલગ શોધી રહ્યાં છો? સેટિંગ્સમાં બીજુ મૂળભૂત શોધ એંજિન પસંદ કરો.
+
+ તમારી મુખ્ય સ્ક્રીન પર શૉર્ટકટ્સ ઉમેરો
+ %1$s માં તમારી મનપસંદ સાઇટ્સ પર ઝડપથી પાછા આવો. ફક્ત %1$s મેનૂમાંથી \"મુખ્ય સ્ક્રીન પર ઉમેરો\" પસંદ કરો.
+
+ ગોપનીયતા એક આદત બનાવો
+ તમારા મૂળભૂત બ્રાઉઝર તરીકે %1$s સેટ કરો અને જ્યારે તમે અન્ય એપ્લિકેશનોથી વેબપૃષ્ઠો ખોલશો ત્યારે ખાનગી બ્રાઉઝિંગના લાભો મેળવો.
+
+ OK, સમજાઇ ગયું!
+ છોડો
+ આગળ
+
+ -
+
+ ઉમેરો
+
+ રદ કરો
+
+ ખાનગી બ્રાઉઝિંગ સત્ર
+
+ સૂચનાઓ તમને નળ સાથે તમારા %1$s સત્રને કાઢી નાખવા દે છે. તમારે એપ્લિકેશન ખોલો અથવા તમારા બ્રાઉઝરમાં શું ચાલી રહ્યું છે તે જોવાની જરૂર નથી.
+
+ બ્રાઉઝિંગ ઇતિહાસને કાઢી નાખો
+
+ Firefox ડાઉનલોડ કરો
+
+ %1$s મોઝીલા અને અન્ય યોગદાનકર્તાઓ દ્વારા બનાવવામાં આવેલ મફત અને ઓપન સોર્સ સૉફ્ટવેર છે.
+
+
+
+
+ વપરાશકર્તા નામ
+ પાસવર્ડ
+ સાફ કરો
+
+ સુરક્ષિત જોડાણ
+ અસુરક્ષિત જોડાણ
+ દ્વારા ખાતરી થઈ: %1$s
+
+ સાઇટ સુરક્ષા
+ URL પહેલાથી અસ્તિત્વમાં
+
+ પાનાંમાં શોધો
+
+ પાનાંમાં શોધો
+
+ %1$d/%2$d
+ %2$d માંથી %1$d
+
+ આગામી પરિણામ શોધો
+ અગાઉના પરિણામ શોધો
+ પાનાંના શોધો કાઢી નાખો
+
+
+
+ ડેસ્કટૉપ સાઇટની વિનંતી કરો
+
+ URL નકલ કરી
+
+ વિકાસકર્તા સાધનો
+
+ અદ્યતન
+
+ USB/Wi-Fi દ્વારા દૂરસ્થ ડિબગીંગ
+
+ આંગળીની ચિહ્ન
+
+ આંગળીના નીશાન માન્ય નથી. ફરીથી પ્રયત્ન કરો.
+
+ આંગળી ખૂબ ઝડપી ખસેડવામાં આવી. ફરીથી પ્રયત્ન કરો.
+
+ ના
+
+ હા
+
+ કેટલાક શોધ એન્જિનો સૂચનો બતાવી શકતા નથી.
+
+ કાઢી નાંખો
+
+
+
+ સાઇટ અણધારી રીતે વર્તે છે? \n ટ્રેકિંગ રક્ષણને બંધ કરી જુઅો
+
+ તમે સૌથી વધુ%1$s ઉપયોગ કરો છો તે સાઇટ્સ પર એક-ટૅપ ઍક્સેસ મેળવો મેનૂ > હોમ સ્ક્રીન પર ઉમેરો
+
+ દરેક લિંકને %1$s માં ખોલો\n ડિફૉલ્ટ બ્રાઉઝર તરીકે %1$s ને સેટ કરો
+
+ તમે સૌથી વધુ ઉપયોગ કરો છો તે સાઇટ્સ માટે સ્વતઃપૂર્ણ URLs\n સરનામાં બારમાં કોઈપણ URL ને લાંબા સમય સુધી દબાવો
+
+ નવી ટેબમાં લિંક ખોલો\n કોઈ પૃષ્ઠ પર કોઈ લિંકને લાંબા સમયથી દબાવો
+
+ પ્રારંભ સ્ક્રીન પર ટિપ્પણીઓ બંધ કરો
+
+ નવી ટૅબ ખૂલી ગઇ છે
+
+ બદલો
+
+ તરત જ નવી ટૅબમાં લિંક પર જાઓ
+
+ સંભવિત જોખમી અને ભ્રામક સાઇટ્સને અવરોધિત કરો
+ રિપોર્ટ કરાયેલી ભ્રામક અને હુમલો કરવાવાળી સાઇટ્સ, મૉલવેરવાળી સાઇટ્સ અને અનિચ્છનીય સૉફ્ટવેરવાળી સાઇટ્સને અવરોધિત કરો.
+
+ અપવાદો
+ તમે આ વેબસાઇટ્સ માટે સામગ્રી અવરોધિત કરવાનું નિષ્ક્રીય કર્યું છે.
+ દૂર કરો
+ બધી વેબસાઇટ્સને દૂર કરો
+
+ ટૅબ ક્રેશ થયું
+ માફ કરશો. અમને આ ટૅબમાં સમસ્યા આવી રહી છે.
+ ખાનગી બ્રાઉઝર તરીકે, અમે આ ટૅબને ક્યારેય સાચવી શકતા નથી અને પુનઃસ્થાપિત કરી શકતા નથી.
+ ટૅબ બંધ કરો
+
+
+
+
+ Mozilla પર ક્રેશ રિપોર્ટ મોકલો
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-hi-rIN/strings.xml b/mobile/android/focus-android/app/src/main/res/values-hi-rIN/strings.xml
new file mode 100644
index 0000000000..e8ee4f553b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-hi-rIN/strings.xml
@@ -0,0 +1,749 @@
+
+
+
+
+
+
+
+
+ रद्द करें
+
+ ठीक है
+
+ सहेजें
+
+
+ खोजें या पता दर्ज करें
+
+ स्वचलित निजी ब्राउज़िंग।\nब्राउज़ करें। मिटाएँ। दोहराएँ।
+
+
+ आपका ब्राउज़िंग इतिहास मिटा दिया गया है।
+ ब्राउज़िंग इतिहास मिटा दिया गया
+
+
+ टैब का ब्राउज़िंग इतिहास मिटा दिया गया है।
+
+
+ %1$s लिए खोजें
+
+
+ साझा करें…
+
+
+ साइट समस्या की रिपोर्ट करें
+
+
+ %1$s में खोलें
+
+
+ इसमें खोलें…
+
+
+ मुख्य स्क्रीन में जोड़ें
+
+
+ शॉर्टकट में जोड़ें
+
+ शॉर्टकट से हटाएँ
+
+ सेटिंग
+ परिचय
+ सहायता
+ आपके अधिकार
+
+
+ ट्रैकर्स अवरुद्ध
+
+
+ इसे बंद करना शायद साइट की कुछ समस्याओं को ठीक कर दे
+
+
+ सामग्री ब्लॉकिंग
+
+ कुछ साइटों को ठीक करने के लिए बंद करें
+
+
+ %1$s द्वारा समर्थित
+
+
+ इसके माध्यम से साझा करें
+
+
+ ब्राउज़िंग इतिहास मिटाएँ
+
+
+ खोलें
+
+
+ मिटा दें और खोलें
+
+
+ मिटाएँ
+
+
+ ब्राउज़िंग इतिहास मिटाएँ
+
+
+
+ मिटाएँ एवं खोलें
+
+
+ मिटाएँ और %1$s खोलें
+
+
+
+ गोपनीयता और सुरक्षा
+
+
+ ट्रैकिंग, कुकीज़, डेटा विकल्प
+
+
+ तयशुदा बनायें, स्वत: पूर्ण
+
+
+
+
+ %1$s परिचय, सहायता
+
+
+ उन्नत ट्रैकिंग सुरक्षा
+
+
+ वेब सामग्री
+
+
+ ऐप्स बदलना
+
+
+ सामान्य
+
+
+ तयशुदा ब्राउज़र, भाषा
+
+
+ आँकड़ा संग्रह व उपयोग
+
+ खोजें
+
+
+ खोज सुझाव पायें
+
+ %1$s उन्हें आपके खोज इंजन में भेजेगा जिन्हें आप पता बार में टाइप करेंगे
+
+
+ मूलभूत
+
+
+ खोज इंजन
+
+
+ चालू
+
+
+ बंद
+
+
+ URL स्वत: पूर्ण
+
+
+ मुख्य साइटों के लिए
+
+
+ आपके द्वारा जोड़े साइटों के लिए
+
+
+ साइटों को प्रबंधित करें
+
+
+ साइटों को प्रबंधित करें
+
+
+ + अपनी संशोधित URL जोड़ें
+
+
+ आपकी स्वतः पूर्ण सूची:
+
+
+ यूआरएल जोड़ें
+
+
+ अपनी संशोधित URL जोड़ें
+
+
+ विशिष्ट URL जोड़ें
+
+
+ स्वतः पूर्णता के लिए लिंक जोड़ें
+
+
+ कुकीज़ और साइट आँकड़ा
+
+
+ आँकड़ा विकल्प
+
+
+ उपभोक्ता संशोधित URL हटाएँ
+
+
+ और जानें
+
+
+ उपभोक्ता संशोधित स्वत: पूर्ण URLs जोड़ें और प्रबंधित करें।
+
+
+ जोड़ने के लिए URL
+
+
+ URL पेस्ट करें या दर्ज करें
+
+
+ उदाहरण: mozilla.org
+
+
+ उदाहरण: example.com
+
+
+ नया उपभोक्ता संशोधित URL जोड़ा गया।
+
+
+ हटाएँ
+
+
+ हटाएँ
+
+
+ आपके द्वारा दर्ज URL की दोहरी जाँच करें।
+
+ भाषा
+
+ सिस्टम डिफ़ॉल्ट
+
+ गोपनीयता
+ विज्ञापन ट्रैकर्स को अवरुद्ध करें
+ कुछ विज्ञापन साइट पर जाने को ट्रैक करते हैं, भले ही आप उन्हें क्लिक न करें
+ विश्लेषणात्मक ट्रैकर्स को अवरुद्ध करें
+ टैपिंग और स्क्रॉलिंग जैसी गतिविधियाँ एकत्र, विश्लेषण और मापने के लिए उपयोग
+ सामाजिक ट्रैकर्स को अवरुद्ध करें
+ साइटों पर आपके आने जाने पर निगाह रखने और साझा बटन जैसी कार्यक्षमता प्रदर्शित करने के लिए अंत:स्थापित
+ अन्य सामग्री ट्रैकर्स को अवरुद्ध करें
+ सक्षम करने पर कुछ पृष्ठ अनपेक्षित व्यवहार कर सकते हैं
+ कुकीज़ बाधित करें
+
+
+ नहीं धन्यवाद
+ केवल तृतीय-पक्ष ट्रैकर कूकीज़ ब्लॉक करें
+ केवल तीसरे पक्ष की कुकीज़ बाधित करें
+ क्रॉस-साइट कुकीज़ को प्रतिबंधित करें
+ हाँ अवश्य
+
+
+ ऐप्प को अनलॉक करने के लिए फिंगरप्रिंट का इस्तेमाल करें
+
+
+ गुप्तता
+
+ एक ऐप्प से दुसरे ऐप्प पर जाते समय वेबपृष्ठों को छिपाएं तथा स्क्रीनशॉट लेना ब्लॉक करें।
+
+ सुरक्षा
+
+ प्रदर्शन
+ वेब लिपियों को अवरुद्ध करें
+
+ अनुपस्थित प्रतीकों या छवियों के रूप में परिणाम हो सकता है
+
+ JavaScript प्रतिबंधित करें
+
+ पृष्ठ तेजी से लोड हो सकते हैं, लेकिन अप्रत्याशित रूप से भी व्यवहार कर सकते हैं
+
+
+ %1$s को डिफ़ॉल्ट ब्राउज़र बनाएँ
+
+ Mozilla
+ उपयोगित डेटा भेजें
+
+
+ और अधिक जानें
+
+
+ Mozilla मात्र वही एकत्र करने का प्रयास करता है जिसकी हमें %1$s को बेहतर बनाने और सबको उपलब्ध कराने के लिए आवश्यकता है।
+
+
+ गोपनीयता सूचना
+
+
+ %1$s परिचय
+
+
+ संस्थापित खोज इंजन
+
+
+ सर्च इंजन चुनें
+
+
+ तयशुदा खोज इंजिन पुनर्बहाल करें
+
+
+ + अन्य खोज इंजन जोड़ें
+ खोज इंजन हटाएँ
+ हटाएँ
+
+ अन्य सर्च इंजन जोड़ें
+
+ अपना पसंदीदा इंजन चुनें:
+
+
+ खोज इंजन जोड़ें
+
+ खोज इंजन का नाम
+ उपयोग के लिए खोज स्ट्रिंग
+ सहेजें
+
+
+ उदाहरण: example.com/search/?q=%s
+
+ नया खोज इंजन संकलित।
+
+ खोज इंजन का नाम दर्ज करें
+ एक मौजूदा खोज इंजन पहले से उस नाम का उपयोग कर रहा है।
+
+ खोज स्ट्रिंग दर्ज करें
+
+ जाँचें कि खोज स्ट्रिंग उदाहरण संरूप से मेल खाती है
+
+
+ इनपुट मिटाएँ
+
+
+ निरस्त करें
+
+
+ ब्राउज़िंग इतिहास मिटाएँ
+
+
+ खुले टैब: %1$s
+
+
+ सुरक्षित संपर्क
+
+
+ लोड हो रहा है
+
+
+ वेबसाइट लोड हो गया
+
+
+ अधिक विकल्प
+
+
+ अन्य विकल्प का बटन
+
+
+ आगे मार्गनिर्देशित करें
+
+
+ वेबसाइट फिर से लोड करें
+
+
+ पीछे मार्गनिर्देशित करें
+
+
+ वेबसाइट लोड करना रोकें
+
+
+ पिछले ऐप पर वापस जाएँ
+
+
+ अवरुद्ध किये गये ट्रैकर्स की संख्या
+
+
+ ट्रैकर्स को अवरुद्ध करें
+
+ आपके अधिकार
+
+ किसी अन्य अनुप्रयोग में लिंक खोलें
+
+ आप इस लिंक को %2$s में खोलने के लिए %1$s को छोड़ सकते हैं।
+
+ एक ऐप ढूंढें जो लिंक खोल सकता है
+
+ आपके डिवाइस पर मौजूद कोई ऐप इस लिंक को खोलने में सक्षम नहीं है। आप एक एप के लिए %2$s की खोज हेतु %1$s को छोड़ सकते हैं जो कर सकता है।
+
+ निजी ब्राउज़िंग से बाहर निकलें?
+
+
+ %1$s खत्म हुआ
+
+
+ खोले
+
+
+
+
+
+
+
+
+
+ सर्वर नहीं मिला
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ अपनी गोपनीयता को बढ़ाएँ
+
+ निजी ब्राउज़िंग को अगले स्तर तक ले जाएँ। विज्ञापन और अन्य सामग्री को ब्लॉक करें जो आपको सभी साइट पर ट्रैक कर सकता है और पृष्ट लोड समय को बढ़ा देता है।
+
+
+ आपका खोज, आपका रास्ता
+
+ कुछ अलग खोज रहे हैं? सेटिंग्स में दूसरा डिफ़ॉल्ट खोज इंजन चुने।
+
+
+ अपने मुख्य स्क्रीन पर शॉर्टकट जोड़ें
+
+ %1$s में अपनी पसंदीदा साइटों को तेज़ी से वापस जाएँ। बस %1$s मेनू से \"मुख्य स्क्रीन पर जोड़ें\" का चयन करें।
+
+
+ गोपनीयता एक आदत बनाये
+
+ %1$s को अपने तयशुदा ब्राउज़र के रूप में सेट करें और जब आप अन्य ऐप्स से वेबपृष्ठ खोलते हैं तब निजी ब्राउज़िंग के फायदे प्राप्त करें।
+
+ ठीक है, समझ गया!
+ छोड़ें
+ अगला
+
+
+ -
+
+
+ जोड़ें
+
+ हाँ
+
+
+ रद्द करें
+
+ नहीं
+
+
+ निजी ब्राउज़िंग सत्र
+
+
+ अधिसूचनाएँ आपको आपका %1$s सत्र एक टैप के साथ मिटाने देती हैं। आपको ऐप खोलने या ब्राउज़र में क्या चल रहा है, देखने की ज़रूरत नहीं है।
+
+
+ ब्राउज़िंग इतिहास मिटाएँ
+
+
+ Firefox डाउनलोड करें
+
+
+
+
+
+
+
+
+ उपयोगकर्ता नाम
+ पासवर्ड
+ साफ करें
+
+
+
+ सुरक्षित संपर्क
+ असुरक्षित संपर्क
+
+ इसके द्वारा जाँचा गया: %1$s
+
+
+ साइट सुरक्षा
+ URL पहले से मौजूद है
+
+
+ पृष्ठ में ढूँढें
+
+
+ पृष्ठ में ढूँढें
+
+
+ %1$d/%2$d
+
+ %2$d में से %1$d
+
+
+ अगला परिणाम पाएँ
+
+ पिछला परिणाम पाएँ
+
+ पृष्ठ में ढूंढें ख़ारिज करें
+
+
+
+
+ डेस्कटॉप साइट के लिए आग्रह करें
+
+
+ डेस्कटॉप साइट
+
+
+ URL की नकल की गई
+
+
+ डेवलेपर औज़ार
+
+
+ ऐप्स में लिंक खोलें
+
+
+ विस्तृत
+
+
+ अध्ययन
+
+
+ Firefox संस्थापन और समय-समय पर अध्ययन कर सकता है.
+
+
+ और अधिक जानें
+
+
+ परिवर्तन लागू हेतु एप्लीकेशन बंद हो जाएगा
+
+
+ हटाएँ
+
+
+ सक्रिय
+
+
+ पूर्ण
+
+
+ यूएसबी/वाई-फाई से दूरस्थ दोषसुधार
+
+
+ लिंक नए सत्र में खोलें
+
+
+ फिंगरप्रिंट चिन्ह
+
+
+ फिंगरप्रिंट अमान्य। पुनः प्रयास करें।
+
+
+ ऊँगली काफी जल्दी हटा ली गयी। पुनः प्रयास करें।
+
+
+ खोज सुझाव दिखाएँ?
+
+
+ नहीं
+
+
+ हाँ
+
+
+ कुछ खोज इंजन सुझाव नहीं दिखा सकते हैं।
+
+
+ निरस्त करें
+
+
+
+
+ साइट अनपेक्षित ढंग से पेश आ रहा है?\n ट्रैकिंग सुरक्षा बंद कर के देखें
+
+
+ होम स्क्रीन में जोड़ें]]>
+
+
+ प्रत्येक लिंक %1$s में खोलें\n %1$s को डिफ़ॉल्ट ब्राउज़र बनायें
+
+
+ स्वतः पूर्ण URLs उन साइटों के लिए जिन्हें आप अधिक इस्तेमाल करते हैं\n पता बार में किसी URL पर कुछ देर दबा कर रखें
+
+
+ कोई लिंक एक नये टैब में खोलें\n पृष्ठ पर किसी लिंक पर कुछ देर दबा कर रखें
+
+
+ प्रारंभ स्क्रीन पर सलाह बंद करें
+
+
+ नया टैब खोल दिया गया
+
+
+ बदलें
+
+
+ तुरंत नए टैब में लिंक पर जाएँ
+
+
+ ख़तरनाक और संदेहास्पद साइटों को ब्लॉक करें
+
+ रिपोर्ट किये हुए भ्रामक तथा अटैक करने वाली साइटें, मैलवेयर वाली साइटें, तथा अवांछित सॉफ्टवेयर वाली साइटों को ब्लॉक करें।
+
+
+ अपवाद
+
+ आपने इन वेबसाइटों के लिए सामग्री ब्लॉकिंग अक्षम कर दिया है।
+
+ हटाएँ
+
+ सभी वेबसाइटों को हटाएँ
+
+
+ कुकीज़ बाधित करें
+
+
+ क्या आप कुकीज़ को बाधित करना चाहेंगे?
+
+
+ टैब क्रैश हो गया
+
+ क्षमा करें। हमें इस टैब के साथ कोई समस्या आ रही है।
+
+ एक निजी ब्राउज़र के तौर पर, हम इस टैब को कभी भी सहेज तथा दुबारा प्राप्त नहीं कर सकते हैं।
+
+ टैब बंद करें
+
+
+
+
+
+ Mozilla को क्रैश रिपोर्ट भेजें
+
+
+
+
+ %s से ट्रैकर अवरूद्ध
+
+ सामग्री
+
+ विज्ञापन
+
+ सामाजिक
+
+ विश्लेषण
+
+ उन्नत ट्रैकिंग सुरक्षा
+
+ इस साइट के लिए सुरक्षा बंद हैं
+
+ इस साइट के लिए सुरक्षा चालू हैं
+
+ संपर्क सुरक्षित है
+
+ संपर्क सुरक्षित नहीं है
+
+
+ वापस जाएँ
+
+
+
+ हटाएँ
+
+
+
+ थीम
+
+ हल्का
+
+ गहरा
+
+ बैटरी सेवर द्वारा सेट करें
+
+ उपकरण थीम को फॉलो करें
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-hr/strings.xml b/mobile/android/focus-android/app/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000000..d78d25298d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-hr/strings.xml
@@ -0,0 +1,1035 @@
+
+
+
+
+
+
+
+
+ Otkaži
+
+ U redu
+
+ Spremi
+
+
+ Pretraži ili upiši adresu
+
+ Automatsko privatno pretraživanje.\nPretražuj. Obriši. Ponovi.
+
+
+ Povijest pregledavanja je obrisana.
+ Povijest pregledavanja očišćena
+
+
+ Povijest pretraživanja kartice je obrisana.
+
+
+ Pretraži %1$s
+
+
+ Dijeli…
+
+
+ Prijavi problem sa stranicom
+
+
+ Otvori u %1$s
+
+
+ Otvori u…
+
+
+ Dodaj na početni zaslon
+
+
+ Dodaj u prečace
+
+ Ukloni iz prečaca
+
+ Postavke
+ Informacije
+ Pomoć
+ Vaša prava
+
+
+ Blokirani programi za praćenje
+
+
+ Isključivanje ovoga može riješiti probleme na nekim stranicama
+
+
+ Blokiranje sadržaja
+
+ Isključi za ispravljanje nekih stranica
+
+
+ Omogućuje %1$s
+
+
+ Dijeli putem
+
+
+ Obriši povijest pretraživanja
+
+
+ Otvori
+
+
+ Obriši i otvori
+
+
+ Obriši
+
+
+ Obriši povijest pretraživanja
+
+
+
+ Obriši i otvori
+
+
+ Obriši i otvori %1$s
+
+
+
+ Pretraži u Focusu
+
+ Pretraži u Klaru
+
+ Pretraži u Focusu Beta
+
+ Pretraži u Focusu Nightly
+
+
+ %1$s daje ti kontrolu.
+Koristi ga kao privatni pretraživač:
+
+ traži i pretražuj direktno u aplikaciji
+ blokiraj programe za praćenje (ili ih omogući u postavkama)
+ izbriši sve kolačiće kao i povijest pretraživanja i pregledavanja
+
+
+%1$s je kreirala Mozilla. Naša misija je njegovanje zdravog i otvorenog interneta.
+Saznaj više
]]>
+
+
+ Privatnost i sigurnost
+
+
+ Praćenje, kolačići, izbori podataka
+
+
+ Postavi zadanu tražilicu, automatsko ispunjavanje
+
+
+
+
+ O %1$su, pomoć
+
+
+ Poboljšana zaštita od praćenja
+
+
+ Web-sadržaj
+
+
+ Prebacivanje između aplikacija
+
+
+ Općenito
+
+
+ Zadani preglednik, jezik
+
+
+ Prikupljanje i uporaba podataka
+
+ Traži
+
+
+ Prikaži prijedloge pretraživanja
+
+ %1$s slat će što pišeš u adresnoj traci tvojoj tražilici
+
+
+ Zadano
+
+
+ Tražilica
+
+
+ Uključeno
+
+
+ Isključeno
+
+
+ Automatsko popunjavanje URL-a
+
+
+ Za omiljene stranice
+
+
+ Omogući da %s automatski dovršava više od 450 popularnih URL-ova u adresnoj traci.
+
+
+ Za stranice koje dodaš
+
+
+ Omogući da %s automatski dovršava tvoje omiljene URL-ove.
+
+
+ Upravlja stranicama
+
+
+ Upravlja stranicama
+
+
+ + Dodaj prilagođeni URL
+
+
+ Tvoj popis za automatsko ispunjavanje:
+
+
+ Dodaj URL
+
+
+ Dodaj prilagođeni URL
+
+
+ Dodaj prilagođeni URL
+
+
+ Dodaj poveznicu u automatsko ispunjavanje
+
+
+ Kolačići i podatci stranica
+
+
+ Izbori podataka
+
+
+ Ukloni prilagođene URL-ove
+
+
+ Saznaj više
+
+
+ Dodaj i upravljaj prilagođenim automatski popunjenim URL-ovima.
+
+
+ URL za dodati
+
+
+ Zalijepi ili unesi URL
+
+
+ Primjer: mozilla.org
+
+
+ Primjer: example.com
+
+
+ Novi prilagođeni URL dodan.
+
+
+ Ukloni
+
+
+ Ukloni
+
+
+ Provjeri URL koji je upisan.
+
+ Jezik
+
+ Zadano sustavom
+
+ Privatnost
+ Blokiraj praćenje oglasa
+
+ Neki oglasi prate posjete stranicama, čak i kada ne klikneš na njih
+ Blokiraj analitičke programe za praćenje
+ Koristi se za prikupljanje, analizu i mjerenje aktivnosti poput dodirivanja i pomicanja
+ Blokiraj društvene programe za praćenje
+ Ugrađeno na web-stranice za praćenje tvojih posjeta i prikaz značajki kao što su gumbi za dijeljenje
+ Blokiraj druge programe za praćenje
+ Omogućavanje ove postavke može uzrokovati neočekivano ponašanje nekih stranica
+ Blokiraj kolačiće
+
+
+ Ne, hvala
+
+ Samo blokiraj kolačiće za praćenje trećih strana
+ Samo blokiraj kolačiće trećih strana
+ Blokiraj međustranične kolačiće
+ Da, molim
+
+
+ Koristi otisak prsta za otključavanje aplikacije
+
+
+ Tajni način
+
+
+ Sakrij web-stranice kod prebacivanja između aplikacija i blokiraj snimanje zaslona.
+
+ Sigurnost
+
+ Performansa
+ Blokiraj web fontove
+
+
+ Može prouzrokovati nestajanje ikona ili slika
+
+ Blokiraj JavaScript
+
+ Stranice će se brže učitavati, ali možda će se i ponašati neočekivano
+
+
+ Učini %1$s zadanim preglednikom
+
+ Mozilla
+ Šalji podatke o korištenju
+
+
+ Saznaj više
+
+
+ Mozilla nastoji sakupljati samo onoliko koliko nam treba da osiguramo i poboljšamo %1$s za sve.
+
+
+ Politika privatnosti
+
+
+ Informacije o licenciranju
+
+
+ Biblioteke koje koristimo
+
+
+ %s | OSS biblioteke
+
+
+ O %1$su
+
+
+ Instalirani pretraživači
+
+
+ Odaberi tražilicu
+
+
+ Vrati zadane pretraživače
+
+
+ + Dodaj pretraživač
+ Ukloni pretraživače
+ Ukloni
+
+ Dodaj drugu tražilicu
+
+ Odaberi omiljenu tražilicu:
+
+
+ Dodaj pretraživač
+
+ Ime pretraživača
+ Tekst pretraživanja za korištenje
+ Spremi
+
+
+ Primjer: example.com/search/?q=%s
+
+ Novi pretraživač je dodan.
+
+ Unesi naziv tražilice
+ Instalirani pretraživač već koristi to ime.
+
+ Unesi tekst za pretraživanje
+
+ Provjeri odgovara li tekst za pretraživanje primjeru
+
+
+ Obriši unos
+
+
+ Odbaci
+
+
+ Obriši povijest pretraživanja
+
+
+ Otvorenih kartica: %1$s
+
+
+ Sigurna veza
+
+
+ Učitavanje
+
+
+ Stranica učitana
+
+
+ Više mogućnosti
+
+
+ Gumb za više opcija
+
+
+ Idi naprijed
+
+
+ Osvježi web stranicu
+
+
+ Idi natrag
+
+
+ Zaustavi učitavanje stranice
+
+
+ Povratak na prethodnu aplikaciju
+
+
+ Broj blokiranih programa za praćenje
+
+
+ Blokiraj programe za praćenje
+
+ Vaša prava
+
+ Otvori poveznicu u drugoj aplikaciji
+
+ Možeš ostaviti %1$s otvoren da bi poveznica bila otvorena u aplikaciji %2$s.
+
+ Pronađi aplikaciju koja može otvoriti poveznicu
+
+ Nijedna aplikacija na tvojem uređaju ne može otvoriti ovu poveznicu. Možeš napustiti %1$s za pronalaženje aplikacije u %2$s.
+
+ Izaći iz privatnog pretraživanja?
+
+
+ %1$s dovršen
+
+
+ Otvori
+
+
+
+
+
+
+
+
+
+ Dodano u prečace!
+
+ Poslužitelj nije pronađen
+
+
+
+
+
+
+
+
+
+
+
+
+ Zatvori
+
+
+
+ Dobro došao, dobro došla u %1$s
+
+
+ Brzo. Privatno. Bez ometanja.
+
+
+ Započni
+
+
+
+ %1$s nije poput drugih preglednika
+
+
+
+ Brišemo tvoju povijest kad zatvoriš program za dodatnu privatnost.
+
+
+
+
+ Postavi %1$s kao standardni progam za štićenje tvojih podataka sa svakom poveznicom koju otvoriš.
+
+
+ Postavi kao standardni preglednik
+
+
+ Preskoči
+
+
+
+ Osnaži svoju privatnost
+
+
+ Podigni privatno pregledavanje na novu razinu. Blokiraj oglase i drugi sadržaj koji te može pratiti među stranicama i usporiti njihovo učitavanje.
+
+
+ Tvoja pretraga na tvoj način
+
+
+ Tražiš nešto drugačije? Izaberi drugu zadanu tražilicu u postavkama.
+
+
+ Dodaj prečice na svoj početni zaslon
+
+
+ Brzo se vrati na svoje omiljene stranice u %1$su. Samo odaberi \"Dodaj na početni zaslon\" u %1$sovom izborniku.
+
+
+ Učini privatnost navikom
+
+
+ Postavi %1$s kao zadani preglednik i iskoristi prednosti privatnog pretraživanja kada otvaraš web-stranice iz drugih aplikacija.
+
+ U redu, razumijem!
+ Preskoči
+ Dalje
+
+
+ -
+
+
+ Dodaj
+
+ DA
+
+
+ Otkaži
+
+ NE
+
+
+ Prečac će biti otvoren s onemogućenom poboljšanom zaštitom od praćenja
+
+
+ Sesija privatnog pretraživanja
+
+
+ Obavijesti ti omogućuju brisanje sesije u %1$su jednim dodirom. Ne moraš otvoriti aplikaciju ili vidjeti što je otvoreno u tvom pregledniku.
+
+
+ Obriši povijest pretraživanja
+
+
+ Preuzmi Firefox
+
+
+
+
+
+ Mozilla Public License i drugih licencija otvorenog kôda.]]>
+
+
+ ovdje.]]>
+
+
+ licencijama.]]>
+
+
+ GNU General Public License v3 i dostupnima ovdje .]]>
+
+
+ Korisničko ime
+ Lozinka
+ Očisti
+
+
+
+ Sigurna veza
+ Nesigurna veza
+
+ Potvrdio: %1$s
+
+
+ Sigurnost stranice
+ URL već postoji
+
+
+ Pronađi na stranici
+
+
+ Pronađi na stranici
+
+
+ %1$d/%2$d
+
+ %1$d od %2$d
+
+
+
+
+ Pronađi sljedeći rezultat
+
+ Pronađi prethodni rezultat
+
+ Odbaci pretraživanje stranice
+
+
+ Zatraži klasičnu stranicu
+
+
+ Klasična stranica
+
+
+ URL je kopiran
+
+
+ Alati za razvijatelje
+
+
+ Otvori poveznice u aplikacijama
+
+
+ Napredno
+
+
+ Dozvole web-stranice
+
+
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Uključeno
+
+
+ Isključeno
+
+
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+ -->
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Uključeno za ovu stranicu
+
+
+ Stranica trenutno nije podržana
+
+
+ Isključeno za ovu stranicu
+
+
+ Smanjivanje pojavljivanja dijaloga kolačića
+
+
+ Isključeno za ovu stranicu
+
+
+ Uključeno za ovu stranicu
+
+
+ Odustani
+
+
+ Zatraži podršku
+
+
+ Automatska reprodukcija
+
+
+ Dozvoli na sljedeći način:
+
+
+ 1. Idi na Android postavke
+
+
+ Dozvole]]>
+
+
+ Idi na postavke
+
+
+ %1$s na UKLJUČENO]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Lokacija
+
+
+ Obavijest
+
+
+ Sadržaj kontroliran DRM-om
+
+
+ Zatraži dozvolu
+
+
+ Blokirano
+
+
+ Dozvoljeno
+
+
+ Blokirano od Androida
+
+
+ Dozvoli zvuk i video
+
+
+ Blokiraj samo zvuk
+
+
+ Preporučeno
+
+
+ Blokiraj zvuk i video
+
+
+ Studije
+
+
+ Firefox može s vremena na vrijeme instalirati i pokretati studije.
+
+
+ Saznaj više
+
+
+ Aplikacija će se zatvoriti da bi se primijenile promjene
+
+
+ Ukloni
+
+
+ Aktivno
+
+
+ Završeno
+
+
+ Udaljeno otklanjanje grešaka putem USB-a/WiFi-ja
+
+
+ Otključaj
+
+
+ Potvrdi otiskom prsta
+
+
+ Za nastavljanje trenutačne sesije programa možeš koristiti svoj otisak prsta.
+
+
+ Otvori poveznicu u novoj sesiji
+
+
+ Ikona otiska prsta
+
+
+ Otisak prsta nije prepoznat. Pokušaj ponovno.
+
+
+ Prst je prebrzo maknut. Pokušaj ponovno.
+
+
+ Prikazati prijedloge pretraživanja?
+
+
+ Za primanje prijedloga, %1$s mora slati tražilici ono što upisuješ u adresnu traku.
+
+
+ Ne
+
+
+ Da
+
+
+ Neke tražili ne mogu prikazivati prijedloge.
+
+
+ Odbaci
+
+
+
+
+ Stranica se ponaša neočekivano?\n
+ Pokušaj isključiti zaštitu od praćenja
+
+
+ Dodaj na početni zaslon]]>
+
+
+ Otvori svaku poveznicu u %1$su\n
+ Postavi %1$s kao zadani preglednik
+
+
+
+ Automatski ispuni URL-ove često korištenih stranica\n
+ Dugo pritisni bilo koji URL u adresnoj traci
+
+
+
+ Otvori poveznicu u novoj kartici\n
+ Dugo pritisni bilo koju poveznicu na stranici
+
+
+
+ Isključi savjete na početnom zaslonu
+
+
+ Otvorena nova kartica
+
+
+ Prebaci
+
+
+ Odmah prebaci na poveznicu u novoj kartici
+
+
+ Blokiraj potencijalno opasne i zavaravajuće stranice
+
+ Blokiraj prijavljene zavaravajuće i napadačke stranice, stranice zaražene zlonamjernim softverom i stranice neželjenog softvera.
+
+
+ Način rada "Samo HTTPS"
+
+
+ Automatski se pokušava povezati s web stranicama koristeći HTTPS šifriranje za povećanu sigurnost.
+
+
+ Iznimke
+
+ Onemogućeno je blokiranje sadržaja za ove stranice.
+
+ Ukloni
+
+ Ukloni sve stranice
+
+
+ Blokiraj kolačiće
+
+
+ Želiš li blokirati kolačiće?
+
+
+ Kartica se srušila
+
+ Oprosti. Imamo problem s ovom karticom.
+
+ Kao privatni preglednik, nikad ne spremamo povijest i ne možemo vratiti ovu karticu.
+
+ Zatvori karticu
+
+
+ Pošalji Mozilli izvještaj o rušenju
+
+
+
+
+ Programa za praćenje blokirano od %s
+
+ Sadržaj
+
+ Oglašavanje
+
+ Društveno
+
+ Analitika
+
+ Poboljšana zaštita od praćenja
+
+ Za ovu web-stranicu, zaštite su ISKLJUČENE
+
+ Za ovu web-stranicu, zaštite su UKLJUČENE
+
+ Veza je sigurna
+
+ Veza nije sigurna
+
+ Programi za praćenje i skripte koje će se blokirati
+
+
+ Idi natrag
+
+
+
+ Ukloni
+
+
+ Preimenuj
+
+ Preimenuj
+
+
+ Ime prečaca
+
+
+ Spremljene i podijeljene slike <b>neće biti</b> izbrisane kada izbrišeš %1$su povijest
+
+
+
+ Tema
+
+ Svijetla
+
+ Tamna
+
+ Postavljeno u postavkama za štednju baterije
+
+ Slijedi temu uređaja
+
+
+
+ Veza nije sigurna
+
+
+
+ Zatvori karticu
+
+
+ Zatvori skočni prozor
+
+
+
+ Zaštićen/a si!
+
+ Odbaci
+
+
+ Dodirni ovdje za bacanje u smeće – povijest, kolačiće, sve – i počni ispočetka na novoj kartici.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Zatvori
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Programčić za pretraživanje
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Povijest pregledavanja je izbrisana! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Dodaj programčić na početni ekran
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Programčić je dodan na početni ekran
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-hsb/strings.xml b/mobile/android/focus-android/app/src/main/res/values-hsb/strings.xml
new file mode 100644
index 0000000000..89cb23f53a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-hsb/strings.xml
@@ -0,0 +1,1119 @@
+
+
+
+
+
+
+
+
+ Přetorhnyć
+
+ W porjadku
+
+ Składować
+
+
+ Adresu pytać abo zapodać
+
+ Awtomatiski priwatny modus.\nPřehladować. Zhašeć. Wospjetować.
+
+
+ Waša přehladowanska historija je so zhašała.
+ Přehladowanska historija je so zhašała
+
+
+ Přehladowanska historija rajtark je so zhašała.
+
+
+ Pytanje za %1$s
+
+
+ Dźělić…
+
+
+ Sydłowy problem zdźělić
+
+
+ W %1$s wočinić
+
+
+ Wočinić w…
+
+
+ Startowej wobrazowce přidać
+
+
+ Zwjazanjam přidać
+
+ Ze zwjazanjow wotstronić
+
+
+ Nastajenja
+ Wo
+ Pomoc
+ Waše prawa
+
+
+ Přesćěhowaki zablokowane
+
+
+ Hdyž to znjemóžnjeće, so někotre sydłowe problemy rozrisaja
+
+
+ Blokowanje wobsaha
+
+ Znjemóžnić, zo byšće někotre sydła porjedźił
+
+
+ Spěchowany wot %1$s
+
+
+ Dźělić přez
+
+ Přehladowansku historiju zhašeć?
+ Podótkńće so tuteje zdźělenki abo zhašejće ju, zo byšće swoju přehladowansku historiju wěsće zhašał.
+
+
+ Podótkńće so tuteje zdźělenki abo zjědźće přez ju, zo byšće swoju přehladowansku historiju wěsće zhašał.
+
+ Přehladowansku historiju zhašeć
+
+
+ Wočinić
+
+
+ Zhašeć a wočinić
+
+
+ Zhašeć
+
+
+ Přehladowansku historiju zhašeć
+
+
+
+ Zhašeć a wočinić
+
+
+ %1$s zhašeć a wočinić
+
+
+
+ W Focus pytać
+
+ W Klar pytać
+
+ W Focus Beta pytać
+
+ W Focus Nightly pytać
+
+
+ %1$s da was kontrolu wobchować.
+Wužiwajće jón jako priwatny wobhladowak:
+
+ Pytajće a přehladujće direktnje w nałoženju
+ Blokujće přesćěhowaki (abo aktualizujće nastajenja, zo byšće přesćěhowaki dowolił)
+ Zhašejće placki a pytansku a přehladowansku historiju
+
+
+%1$s so wot Mozilla wuwiwa. Naša misija je spěchowanje stroweho, wotewrjeneho interneta.
+Dalše informacije
]]>
+
+
+ Priwatnosć a wěstota
+
+
+ Slědowanje, placki, datowe wuběry
+
+
+ Standard nastajić, awtomatiske wudospołnjenje
+
+
+
+
+ Wo %1$s, pomoc
+
+
+ Polěpšeny slědowanski škit
+
+
+ Webwobsah
+
+
+ Přešaltowanje nałoženjow
+
+
+ Powšitkowne
+
+
+ Standardny wobhladowak, rěč
+
+
+ Zběranje a wužiwanje datow
+
+ Pytać
+
+
+ Pytanske namjety wobstarać
+
+ %1$s so na wašu pytawu pósćele, štož w adresowym polu zapodaće
+
+
+ Standard
+
+
+ Pytawa
+
+
+ Zapinjeny
+
+
+ Wupinjeny
+
+
+ Awtomatiske wudospołnjenje URL
+
+
+ Za najlubše sydła
+
+
+ Zmóžnić, zo by %s wjace hač 450 woblubowanych adresow w adresowym polu wudospołnił.
+
+
+ Za sydła, kotrež přidawaće
+
+
+ Zmóžnić, zo %s waše najlubše URL awtomatisce wudospołnja.
+
+
+ Sydła rjadować
+
+
+ Sydła rjadować
+
+
+ + Swójsku adresu přidać
+
+
+ Waša lisćina awtowudospołnjenjow:
+
+
+ URL přidać
+
+
+ Swójsku adresu přidać
+
+
+ Swójski URL přidać
+
+
+ Wotkaz za awtowudospołnjenje přidać
+
+
+ Placki a sydłowe daty
+
+
+ Datowy wuběr
+
+
+ Swójske adresy wotstronić
+
+
+ Dalše informacije
+
+
+ Swójske adresy za wudospołnjenje přidać a rjadowác.
+
+
+ Adresa, kotraž ma so přidać
+
+
+ Adresu zasadźić abo zapodać
+
+
+ Přikład: mozilla.org
+
+
+ Přikład: example.com
+
+
+ Nowa swójska adresa přidata.
+
+
+ Wotstronić
+
+
+ Wotstronić
+
+
+ Přepruwujće zapodatu adresu.
+
+ Rěč
+
+ Systemowy standard
+
+ Priwatnosć
+ Wabjenske přesćěhowaki blokować
+ Někotre wbajenje sydłowe wopyty slěduje, samo hdyž na wabjenje njekliknjeće
+ Analyzowe přesćěhowaki blokować
+ Z tym so aktiwity kaž dótkanje a kulenje přez strony hromadźa, analyzuja a měrja
+ Socialne přesćěhowaki blokować
+ Na sydłach zasadźene, zo bychu waše wopyty slědowali a funkcije kaž tłóčatka za dźělenje zwobraznili
+ Druhe wobsahowe přesćěhowaki blokować
+ To móže wjesć k tomu, zo so někotre strony njewšědnje zadźerža
+ Placki blokować
+
+
+ Ně, dźakuju so
+ Jenož slědowace placki třećich poskićowarjow blokować
+ Jenož placki třećich poskićowarjow
+
+ Placki wjacorych sydłow blokować
+ Haj, prošu
+
+
+ Wužiwajće porstowy wotćišć, zo byšće nałoženje wotewrěł
+
+
+ Wotewriće z pomocu porstoweho wotćišća, jeli sće zwjazanja přidał abo hdyž websydło je hižo w %s wočinjene.
+
+
+ Tarnowanje
+
+ Webstrony schować, hdyž so nałoženja přepinaja a fotam wobrazowki zadźěwać.
+
+ Wěstota
+
+ Wukon
+ Webpisma blokować
+
+ Móže k tomu wjesć, zo symbole abo wobrazy faluja
+
+ JavaScript blokować
+
+ Strony so spěšnišo začitaja, móhli so wšak tež na njewočakowane wašnje zadźeržeć
+
+
+ %1$s jako standardny wobhladowak nastajić
+
+ Mozilla
+ Wužiwanske daty pósłać
+
+
+ Dalše informacije
+
+
+ Mozilla so prócuje, jenož informacije hromadźić, z kotrymiž móžemy %1$s za kóždeho poskićić a polěpšić.
+
+
+ Zdźělenka priwatnosće
+
+
+ Licencne informacije
+
+
+ Biblioteki, kotrež wužiwamy
+
+
+ %s | OSS-biblioteki
+
+
+ Wo %1$s
+
+
+ Instalowane pytawy
+
+
+ Pytawu wubrać
+
+
+ Standardne pytawy wobnowić
+
+
+ + Dalšu pytawu přidać
+ Pytawy wotstronić
+ Wotstronić
+
+
+ Dalšu pytawu přidać
+
+ Wubjerće swoju preferowanu pytawu:
+
+
+ Pytawu přidać
+
+ Mjeno pytawy
+ Pytanski wuraz, kotryž ma so wužiwać
+ Składować
+
+
+ Přikład: example.com/search/?q=%s
+
+ Nowa pytawa je so přidała.
+
+ Mjeno pytawy zapodać
+ Je hižo pytawa instalowana, kotraž tute mjeno wužiwa.
+
+ Pytanski wuraz zapodać
+
+ Přepruwujće, hač pytanski wuraz přikładowemu formatej wotpowěduje
+
+
+ Zapodaće wuprózdnić
+
+
+ Zaćisnyć
+
+
+ Přehladowansku historiju zhašeć
+
+
+ Wočinjene rajtarki: %1$s
+
+
+ Wěsty zwisk
+
+
+ Začitanje
+
+
+ Websydło je so začitało
+
+
+ Dalše nastajenja
+
+
+ Tłóčatko dalšich nastajenjow
+
+
+ Doprědka
+
+
+ Websydło znowa začitać
+
+
+ Wróćo
+
+
+ Čitanje websydła zastajić
+
+
+ Wróćo k předchadnemu nałoženju
+
+
+ Ličba zablokowanych přesćěhowakow
+
+
+ Přesćěhowaki blokować
+
+ Waše prawa
+
+ Wotkaz w druhim nałoženju wočinić
+
+ Móžeće %1$s wopušćić, zo byšće tutón wotkaz w %2$s wočinił.
+
+ Nałoženje pytać, kotrež móže wotkaz wočinić
+
+ Žane z nałoženjow na wašim graće njemóže tutón wotkaz wočinić. Móžeće %1$s wopušćić, zo byšće %2$s za nałoženjom přepytował.
+
+ Priwatny modus wopušćić?
+
+
+ %1$s dokónčeny
+
+
+ Wočinić
+
+
+
+
+
+
+
+
+
+
+ Zwjazanjam přidate!
+
+ Serwer namakany njeje
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Začinić
+
+
+
+ Witajće k %1$s
+
+
+ Spěšny. Priwatny. Žane wotwjedźenja.
+
+
+ Prěnje kroki
+
+
+
+ %1$s kaž druhe wobhladowaki njeje
+
+
+ Zhašamy wašu historiju, hdyž nałoženje za přidatnu priwatnosć začinjeće.
+
+
+
+ Čińće %1$s k swojemu standardnemu wobhladowakej, zo byšće swoje dat z kóždym wotkaz, kotryž wočinjeće, škitał.
+
+
+ Jako standardny wobhladowak nastajić
+
+
+ Přeskočić
+
+
+
+ Zesylńće swoju priwatnosć
+
+ Zběhńće priwatne přehladowanje na přichodnu wyšu runinu. Blokujće wabjenje a hinaši wobsah, kotryž móžeće přez sydła slědować a začitanske časy stronow podlěšić.
+
+
+ Waše pytanje kaž wy jo chceće
+
+ Pytaće za něčim druhim? Wubjerće druhu stndardnu pytawu w nastajenjach.
+
+
+ Přidajće swojej startowej wobrazowce tastowe skrótšenki
+
+ Wróćće so spěšnje k swojim najlubšim sydłam w %1$s. Wubjerće prosće „Startowej wobrazowce přidać“ z menija %1$s.
+
+
+ Přiwučće sej priwatnosć
+
+ Nastajće %1$s jako swój standardny wobhladowak a zwužitkujće priwatne přehladowanje, hdyž webstrony z druhich nałoženjow wočinjeće.
+
+ W porjadku, sym zrozumił!
+ Přeskočić
+ Dale
+
+
+ -
+
+
+ Přidać
+
+
+ HAJ
+
+
+ Přetorhnyć
+
+
+ NĚ
+
+
+ Zwjazanje so ze znjemóžnjenym polěpšenym šlědowanskim škitom wočini
+
+
+ Priwatne posedźenje
+
+
+ Zdźělenki wam zmóžnjeja, waše posedźenje %1$s z podótkom zhašeć. Njetrjebaće nałoženje wočinić abo hladać, štož we wašim wobhladowaku běži.
+
+
+ Přehladowansku historiju zhašeć
+
+
+ Firefox sćahnyć
+
+
+
+
+
+
+
+
+ Mozilla Public License a druhimi licencami wotewrjeneho žórła k dispoziciji staja.]]>
+
+
+ tu.]]>
+
+
+ licencach wotewrjeneho žórła k dispoziciji.]]>
+
+
+ General Public Licence v3 wužiwa, kotrež su tu k dispoziciji.]]>
+
+
+ Wužiwarske mjeno
+ Hesło
+ Zhašeć
+
+
+
+ Wěsty zwisk
+ Njewěsty zwisk
+
+ Přepruwowany wot: %1$s
+
+
+ Sydłowa wěstota
+ URL hižo eksistuje
+
+
+ Na stronje pytać
+
+
+ Na stronje pytać
+
+
+ %1$d/%2$d
+
+ %1$d z %2$d
+
+
+ Přichodny wuslědk namakać
+
+ Předchadny wuslědk namakać
+
+ Na stronje pytać znjemóžnić
+
+
+
+
+ Desktopowe sydło požadać
+
+
+ Desktopowe sydło
+
+
+ URL kopěrowany
+
+
+ Wuwiwarske nastroje
+
+
+ Wotkazy w nałoženjach wočinić
+
+
+ Rozšěrjene
+
+
+ Sydłowe prawa
+
+
+ Redukowanje plackowych chorhojow
+
+
+ Zmóžnjeny
+
+
+ Znjemóžnjeny
+
+
+ Redukowanje plackowych chorhojow
+
+
+ Wotpokazajće plackowe naprašowanja, zo byšće mjenje chorhojow widźał, jeli móžno.
+
+ -->
+ Redukowanje plackowych chorhojow
+
+
+ Za tute sydło ZMÓŽNJENY
+
+
+ Sydło so tuchwilu njepodpěruje
+
+
+ Za tute sydło ZNJEMÓŽNJENY
+
+
+ Redukowanje plackowych chorhojow
+
+
+ Za tute sydło ZNJEMÓŽNJENY
+
+
+ Za tute sydło ZMÓŽNJENY
+
+
+ Redukowanje plackowych chorhojow za %1$s zmóžnić?
+
+
+ Redukowanje plackowych chorhojow za %1$s znjemóžnić?
+
+
+ %1$s placki sydła zhaša a budźe stronu aktualizować. Zhašenje wšěch plackow móže was přizjewić abo nakupowanske wozyčki wuprózdnić.
+
+
+ %1$s móže plackowe naprašowanja awtomatisce wotpokazać.
+
+
+ Tute sydło so tuchwilu přez redukciju plackowych chorhojow. Chceće naš team prosyć, tute websydło přepruwować a pomoc w přichodźe přidać?
+
+
+ Přetorhnyć
+
+
+ Pomoc požadać
+
+
+ Naprašowanje na sydło pomocy je so wotpósłało.
+
+
+ Naprašowanje na sydło pomocy je so wotpósłało.
+
+
+
+ %1$s pospytuje plackowe naprašowanja wotpokazować, zo by njepřihódne plackowe chorhoje zaćisnył.\n\nRjadujće nastajenja plackowych chorhojow w %2$s.
+
+ nastajenja
+
+
+ Awtomatiske wothraće
+
+
+ Zo byšće to dowolił:
+
+
+ 1. Dźiće k nastajenjam Android
+
+
+ Berechtigungen (Prawa)]]>
+
+
+ K nastajenjam
+
+
+ %1$s]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Městno
+
+
+ Zdźělenka
+
+
+ Wobsah wodźeny přez DRM
+
+
+ Wo dowolnosć so prašeć
+
+
+ Zablokowany
+
+
+ Dowoleny
+
+
+ Přez Android zablokowane
+
+
+ Awdio a widejo dowolić
+
+
+ Jenož awdio blokować
+
+
+ Doporučene
+
+
+ Awdio a widejo blokować
+
+
+ Studije
+
+
+ Firefox móže hdys a hdys studije instalować a přewjesć.
+
+
+ Dalše informacije
+
+
+ Nałoženje so skónči, zo bychu so změny wuskutkowali
+
+
+ Wotstronić
+
+
+ Aktiwny
+
+
+ Dokónčene
+
+
+ Zdalene pytanje zmylkow přez USB/WLAN
+
+
+ Wotewrěć
+
+
+ Wobkrućće z pomocu swojeho porstoweho wotćišća
+
+
+ Móžeće swój porstowy wotćišć, zo byšće z aktualnym posedźenjom swojeho nałoženja pokročował.
+
+
+ Wotkaz w nowym posedźenju wočinić
+
+
+ Symbol porstoweho wotćišća
+
+
+ Porstowy wotćišće njeje so spóznał. Spytajće hišće raz.
+
+
+ Porst je so přespěšnje pohibnył. Spytajće hišće raz.
+
+
+ Pytanske namjety pokazać?
+
+
+ Zo byšće namjety dóstał, dyrbi %1$s wobsah, kotryž sće do adresoweho pola zapodał, na pytawu słać.
+
+
+ Ně
+
+
+ Haj
+
+
+ Někotre pytawy njemóža namjety pokazać.
+
+
+ Zaćisnyć
+
+
+
+
+ Sydło so na njewočakowane wašnje zadźerži?\nSpytajće slědowanski škit znjemóžnić
+
+
+ Startowej wobrazowce přidać]]>
+
+
+ Kóždy wotkaz w %1$s wočinić\n%1$s jako standardny wobhladowak nastajić
+
+
+ URL za sydła, kotrež najhusćišo wopytujeće, awtomatisce wudospołnić\nTłóčće dołho na URL w adresowym polu
+
+
+ Wotkaz w nowym rajtarku wočinić\nTłóčće dołho na wotkaz na stronje
+
+
+ Pokiwy na starrtowej wobrazowce znjemóžnić
+
+
+ Nowy rajtark je so wočinił
+
+
+ Přepinać
+
+
+ Połna wobrazowka so pokazuje
+
+
+ Hnydom k wotkazej w nowym rajtarku přeńć
+
+
+ Potencielnje strašne a wobšudne sydła blokować
+
+ Blokujće zdźělene wobšudne a nadpadowe sydła, škódne sydła a sydła z njewitanej softwaru.
+
+
+ Modus Jenož-HTTPS
+
+
+ Pospytuje z pomocu zaklučowanskeho protokola HTTPS za powyšenu wěstotu awtomatisce ze sydłami zwjazać.
+
+
+ Wuwzaća
+
+ Sće blokowanje wobsaha za tute sydła znjemóžnił.
+
+ Wotstronić
+
+ Wšě sydła wotstronić
+
+
+ Placki blokować
+
+
+ Chceće placki blokować?
+
+
+ Rajtark je spadnył
+
+ Bohužel mamy problem z tutym rajtarkom.
+
+ Jako priwatny wobhladowak njeskładujemy ženje a njemóžemy tutón rajtark wobnowić.
+
+ Rajtark začinić
+
+
+
+
+
+ Mozilla spadowu rozprawu pósłać
+
+
+
+
+ Přesćěhowaki su zablokowane wot %s
+
+ Wobsah
+
+ Wabjenje
+
+ Socialne syće
+
+ Analytika
+
+ Polěpšeny slědowanski škit
+
+ Škit je znjemóžnjeny za tute sydło
+
+ Škit je zmóžnjeny za tute sydło
+
+ Zwisk je wěsty
+
+ Zwisk wěsty njeje
+
+
+ Přesćěhowaki a skripty, kotrež so maja blokować
+
+
+ Wróćo hić
+
+
+
+ Wotstronić
+
+
+ Přemjenować
+
+ Přemjenować
+
+
+ Mjeno zwjazanja
+
+
+ Składowane a dźělene wobrazy <b>so nje</b>zhašeja, hdyž historiju %1$s zhašeće.
+
+
+
+ Drasta
+
+ Swětły
+
+ Ćmowy
+
+ Po zalutowanskich nastajenjach baterije
+
+ Na gratowu drastu dźiwać
+
+
+
+ Tute sydło HTTPS njepodpěruje
+
+
+ Dalše informacije
+ Změńće tute nastajenje w Nastajenja > Priwatnosć a wěstota > Wěstota.]]>
+
+
+ Zwisk njewěsty
+
+
+
+
+ Jeli sće w zańdźenosći ze serwerom wuspěšnje zwjazany był, móže zmylk nachwilny być.
+ ]]>
+
+
+ Něchtó móhł spytać, sydło za swoje wudać a pokročowanje móhło riskantne być.
+
+ %1$s %2$s njedowěrja, dokelž jeho certifikatowy wudawar je njeznaty, certifkat je samsignowany abo serwer korektne mjezycertifikaty njesćele.
+ ]]>
+
+
+
+ Rajtark začinić
+
+
+
+ Lepjene! Smy sydło při tym haćili was wuskušować. Podótkńće so kóždy čas tarča, zo byšće widźał, štož my blokujemy.
+
+
+ Wuskakowace wokno začinić
+
+
+
+ Sće škitany!
+
+ Tute standardne nastajenja mócny škit poskićeja. Ale je lochko, nastajenja wašim specifiskim potrjebam přměrić.
+
+ Zaćisnyć
+
+
+ Podótkńće so tu, zo byšće wšitko do papjernika přesunył – historiju, placki, wšitko – a startujće znowa na nowym rajtarku.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Začinić
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Pytanski asistent
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Přehladowanska historija je so zhašała! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Započńće swoje priwatne přehladowanske posedźenje a my budźemy přesćěhowaki a druhe škódne elementy při tutej składnosći blokować.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Přewostajamy wašemu priwatnemu modusej, ale startujće přichodny spěšnišo z asistentom %1$s na swojej startowej wobrazowce.
+
+
+ Asistent startowej wobrazowce přidać
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Asistent je so startowej wobrazowce přidał
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-hu/strings.xml b/mobile/android/focus-android/app/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000000..220f06b576
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-hu/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ Mégse
+
+ OK
+
+ Mentés
+
+
+ Keressen, vagy adjon meg címet
+
+ Automatikus privát böngészés.\nBöngészés. Törlés. Ismétlés.
+
+
+ A böngészési előzmények törölve.
+ Böngészési előzmények törölve
+
+
+ A lap böngészési előzményei törölve lettek.
+
+
+ Keresés erre: %1$s
+
+
+ Megosztás…
+
+
+ Hibás webhely bejelentése
+
+
+ Megnyitás ezzel: %1$s
+
+
+ Megnyitás a következővel…
+
+
+ Kezdőképernyőhöz adás
+
+
+ Hozzáadás a kezdőképernyőhöz
+
+ Eltávolítás a kezdőképernyőről
+
+
+ Beállítások
+ Névjegy
+ Súgó
+ Az Ön jogai
+
+
+ Blokkolt követők
+
+
+ Ennek a kikapcsolása megoldhatja egyes webhelyek problémáit
+
+
+ Tartalomblokkolás
+
+ Kikapcsolás egyes webhelyek kijavításához
+
+
+ A motorháztető alatt: %1$s
+
+
+ Megosztás ezzel
+
+ Törli a böngészési előzményeket?
+ Koppintson vagy törölje ezt az értesítést a böngészési előzmények biztonságos törléséhez.
+
+
+ Koppintson vagy csúsztassa oldalra ezt az értesítést a böngészési előzmények biztonságos törléséhez.
+
+ Előzmények törlése
+
+
+ Megnyitás
+
+
+ Törlés és megnyitás
+
+
+ Törlés
+
+
+ Böngészési előzmények törlése
+
+
+
+ Törlés és megnyitás
+
+
+ Törlés és a %1$s megnyitása
+
+
+
+ Keresés a Focusban
+
+ Keresés a Klarban
+
+ Keresés a Focus Bétában
+
+ Keresés a Focus Nightlyban
+
+
+ A %1$s az Ön kezébe adja az irányítást.
+Használja privát böngészőként:
+
+ Keressen és böngésszen közvetlenül az appban
+ Blokkolja a követőket (vagy frissítse a beállításokat az engedélyezéshez)
+ Törölje a sütiket és a böngészési előzményeket is
+
+
+A %1$s a Mozilla terméke. Küldetésünk az egészséges, nyílt Internet támogatása.
+Tudjon meg többet
]]>
+
+
+ Adatvédelem és biztonság
+
+
+ Követés, sütik, adatküldések
+
+
+ Beállítás alapértelmezettként, automatikus kiegészítés
+
+
+
+
+ A %1$s névjegye, súgó
+
+
+ Fokozott követés elleni védelem
+
+
+ Webtartalom
+
+
+ Appok közti váltás
+
+
+ Általános
+
+
+ Alapértelmezett böngésző, nyelv
+
+
+ Adatgyűjtés és -felhasználás
+
+ Keresés
+
+
+ Keresési javaslatok kérése
+
+ A %1$s elküldi a címsávba írt szöveget a keresőszolgáltatásának
+
+
+ Alapértelmezett
+
+
+ Keresőszolgáltatás
+
+
+ Be
+
+
+ Ki
+
+
+ Automatikus URL kiegészítés
+
+
+ Leggyakrabban felkeresett oldalak esetén
+
+
+ Engedélyezés, hogy a %s 450 népszerű URL-t automatikusan kiegészítsen a címsávban.
+
+
+ Hozzáadott oldalak esetén
+
+
+ Engedélyezés, hogy a %s kiegészítse a kedvenc URL-jeit.
+
+
+ Oldalak kezelése
+
+
+ Oldalak kezelése
+
+
+ + Egyéni URL hozzáadása
+
+
+ Az automatikus kiegészítései:
+
+
+ URL hozzáadása
+
+
+ Egyéni URL hozzáadása
+
+
+ Egyéni URL hozzáadása
+
+
+ Hivatkozás hozzáadása az automatikus kiegészítéshez
+
+
+ Sütik és oldaladatok
+
+
+ Adatküldések
+
+
+ Egyéni URL-ek eltávolítása
+
+
+ További tudnivalók
+
+
+ Automatikusan kiegészített URL-ek hozzáadása és kezelése.
+
+
+ Hozzáadandó URL-ek
+
+
+ Illessze be, vagy adja meg az URL-t
+
+
+ Példa: mozilla.org
+
+
+ Példa: example.com
+
+
+ Új egyéni URL hozzáadva.
+
+
+ Eltávolítás
+
+
+ Eltávolítás
+
+
+ Ellenőrizze a megadott URL-t.
+
+ Nyelv
+
+ Rendszer alapbeállítása
+
+ Adatvédelem
+ Hirdetéskövetők blokkolása
+ Néhány hirdetés követi az oldallátogatásokat, még akkor is, ha nem kattint a hirdetésekre
+ Analitikai követők blokkolása
+ Arra használatosak, hogy begyűjtsék, elemezzék és mérjék az olyan tevékenységeket, mint az érintés vagy a görgetés
+ Közösségi oldalak követőinek blokkolása
+ Az oldalakba vannak ágyazva, hogy kövessék a látogatásait, és megosztás gombokat jelenítsenek meg
+ Más tartalomkövetők blokkolása
+ Az engedélyezés néhány oldalon váratlan működést okozhat
+ Sütik blokkolása
+
+
+ Köszönöm, nem
+ Csak a harmadik féltől származó követők sütijeinek blokkolása
+ Csak a harmadik féltől származó sütik törlése
+
+ Webhelyek közötti sütik blokkolása
+ Igen, kérem
+
+
+ Ujjlenyomat használata az alkalmazás feloldásához
+
+
+ Oldja fel a zárolást ujjlenyomattal, ha adott hozzá parancsikonokat, vagy ha egy webhely már meg van nyitva a %sban.
+
+
+ Rejtőzködés
+
+ Weboldalak elrejtése alkalmazásváltáskor, és a képernyőkép készítés letiltása.
+
+ Biztonság
+
+ Teljesítmény
+ Webes betűkészletek blokkolása
+
+ Hiányzó ikonokat és képeket eredményezhet
+
+ JavaScript blokkolása
+
+ Az oldalak gyorsabban tölthetnek be, de nem várt módon is viselkedhetnek
+
+
+ A %1$s legyen az alapértelmezett böngésző
+
+ Mozilla
+ Használati adatok elküldése
+
+
+ Tudjon meg többet
+
+
+ A Mozilla arra törekszik, hogy csak azt gyűjtse, ami a %1$s fejlesztéséhez és támogatásához szükséges.
+
+
+ Adatvédelmi nyilatkozat
+
+
+ Licencinformációk
+
+
+ Az általunk használt programkönyvtárak
+
+
+ %s | Nyílt forráskódú programkönyvtárak
+
+
+ A %1$s névjegye
+
+
+ Telepített keresőszolgáltatások
+
+
+ Válasszon keresőszolgáltatást
+
+
+ Alapértelmezett keresőszolgáltatások visszaállítása
+
+
+ + További keresőszolgáltatás felvétele
+ Keresőszolgáltatások eltávolítása
+ Eltávolítás
+
+
+ További keresőszolgáltatás felvétele
+
+ Válassza ki az előnyben részesített keresőszolgáltatását:
+
+
+ Keresőszolgáltatás hozzáadása
+
+ Keresőszolgáltatás neve
+ Használandó keresőkifejezés
+ Mentés
+
+
+ Példa: example.com/search/?q=%s
+
+ Új keresőszolgáltatás hozzáadása.
+
+ Adja meg a keresőszolgáltatás nevét
+ Egy telepített keresőszolgáltatás már használja ezt a nevet.
+
+ Adja meg a keresési szöveget
+
+ Ellenőrizze, hogy a keresési szöveg egyezik-e a példa formátumával
+
+
+ Bemenet törlése
+
+
+ Elutasítás
+
+
+ Böngészési előzmények törlése
+
+
+ Nyitott lapok: %1$s
+
+
+ Biztonságos kapcsolat
+
+
+ Betöltés
+
+
+ Weboldal betöltve
+
+
+ További beállítások
+
+
+ További beállítások gomb
+
+
+ Navigálás előre
+
+
+ Webhely újratöltése
+
+
+ Navigálás visszafelé
+
+
+ Webhely betöltésének leállítása
+
+
+ Vissza az előző apphoz
+
+
+ Blokkolt követők száma
+
+
+ Követők blokkolása
+
+ Az Ön jogai
+
+ Hivatkozás megnyitása egy másik appban
+
+ Elhagyhatja a %1$st, hogy itt nyissa meg ezt a hivatkozást: %2$s.
+
+ Keressen egy appot, amely meg tudja nyitni a hivatkozást
+
+ Az eszközön lévő appok egyike sem tudja megnyitni ezt a hivatkozást. Kiléphet a %1$sból, és kereshet egy megfelelőt itt: %2$s.
+
+ Kilép a privát böngészésből?
+
+
+ %1$s kész
+
+
+ Megnyitás
+
+
+
+
+
+
+
+
+
+
+ Hozzáadva az indítóikonokhoz.
+
+ A kiszolgáló nem található
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Bezárás
+
+
+
+ Üdvözli a %1$s
+
+
+ Gyors. Privát. Zavaró tényezők nélkül.
+
+
+ Kezdő lépések
+
+
+
+ A %1$s nem olyan, mint a többi böngésző
+
+
+ A fokozott adatvédelem érdekében töröljük az előzményeket, amikor bezárja az alkalmazást.
+
+
+
+ Legyen a %1$s az alapértelmezett, hogy minden megnyitott hivatkozásnál megvédje az adatait.
+
+
+
+ Beállítás alapértelmezett böngészőként
+
+
+ Kihagyás
+
+
+
+ Tuningolja fel az adatvédelmét
+
+ Emelje a privát böngészést a következő szintre. Blokkolja a hirdetéseket, és más tartalmakat, amelyek követhetik az oldalak között, és lelassíthatják a betöltési sebességet.
+
+
+ Az Ön keresése, az Ön útja
+
+ Valami mást keres? Válasszon másik alapértelmezett keresőszolgáltatást a Beállításokban.
+
+
+ Parancsikonok hozzáadása a kezdőképernyőhöz
+
+ Térjen vissza gyorsan a kedvenc oldalaihoz a %1$sban. Csak válassza a „Hozzáadás a kezdőképernyőre” lehetőséget a %1$s menüből.
+
+
+ Váljon szokásává az adatvédelem
+
+ Állítsa be a %1$st alapértelmezett böngészőként, és kapja meg a privát böngészés előnyeit, amikor weboldalakat nyit meg a többi appból.
+
+ Rendben, értem!
+ Kihagyás
+ Következő
+
+
+ –
+
+
+ Hozzáadás
+
+
+ IGEN
+
+
+ Mégse
+
+
+ NEM
+
+
+ Az ikon fokozott követés elleni védelem nélkül fogja megnyitni az alkalmazást
+
+
+ Privát böngészés
+
+
+ Az értesítésekkel egy érintéssel törölheti a %1$s munkamenetét. Nem kell megnyitnia az alkalmazást, sem megnéznie, hogy mi fut benne.
+
+
+ Böngészési előzmények törlése
+
+
+ Firefox letöltése
+
+
+
+
+
+
+
+
+ Mozilla Public License és más nyílt forráskódú licencek feltételei szerint érhető el.]]>
+
+
+ itt találhat.]]>
+
+
+ licencek alatt.]]>
+
+
+ GNU General Public Licence v3 alatt, ez itt érhető el.]]>
+
+
+ Felhasználónév
+ Jelszó
+ Törlés
+
+
+
+ Biztonságos kapcsolat
+ Nem biztonságos kapcsolat
+
+ Ellenőrizte: %1$s
+
+
+ Webhely biztonsága
+ Az URL már létezik
+
+
+ Keresés az oldalon
+
+
+ Keresés az oldalon
+
+
+ %1$d/%2$d
+
+ %1$d ennyiből: %2$d
+
+
+ Következő találat
+
+ Előző találat
+
+ Kereső elrejtése
+
+
+
+
+ Asztali oldal kérése
+
+
+ Asztali oldal
+
+
+ URL másolva
+
+
+ Fejlesztői eszközök
+
+
+ Hivatkozások megnyitása alkalmazásokban
+
+
+ Speciális
+
+
+ Oldalengedélyek
+
+
+ Sütibannerek számának csökkentése
+
+
+ Be
+
+
+ Ki
+
+
+ Sütibannerek számának csökkentése
+
+
+ Lásson kevesebb bannert a sütikérések automatikus elutasításával, ha az lehetséges.
+
+ -->
+ Sütibannerek számának csökkentése
+
+
+ BE ezen az oldalon
+
+
+ A webhely jelenleg nem támogatott
+
+
+ KI ezen az oldalon
+
+
+ Sütibannerek számának csökkentése
+
+
+ KI ezen az oldalon
+
+
+ BE ezen az oldalon
+
+
+ Bekapcsolja a sütibanner-csökkentést a következőnél: %1$s?
+
+
+ Kikapcsolja a sütibanner-csökkentést a következőnél: %1$s?
+
+
+ A %1$s törli a webhely sütijeit, és frissíti az oldalt. Az összes süti törlésével kijelentkezhet, vagy kiürítheti a kosarait.
+
+
+ A %1$s megpróbálhatja automatikusan elutasítani a sütikéréseket.
+
+
+ Ez az oldalt jelenleg nem támogatja a Sütibannerek számának csökkentését. Szeretné kérni, hogy csapatunk vizsgálja felül ezt a webhelyet, és támogassa a jövőben?
+
+
+ Mégse
+
+
+ Támogatás kérése
+
+
+ A kérelem elküldve a támogatási webhelynek.
+
+
+ A kérelem elküldve a támogatási webhelynek.
+
+
+
+ A %1$s megpróbálja elutasítani a sütikéréseket, hogy eltüntesse a bosszantó sütibannereket.\n\nA sütibannerek beállításait a %2$s kezelheti.
+
+
+ beállításokban
+
+
+ Automatikus lejátszás
+
+
+ Az engedélyezés módja:
+
+
+ 1. Ugorjon az Android beállításokhoz
+
+
+ Engedélyek lehetőségre]]>
+
+
+ Ugrás a beállításokhoz
+
+
+ %1$s]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Hely
+
+
+ Értesítés
+
+
+ DRM-vezérelt tartalom
+
+
+ Kérdezzen rá
+
+
+ Blokkolva
+
+
+ Engedélyezve
+
+
+ Az Android blokkolta
+
+
+ Hang és videó engedélyezése
+
+
+ Csak a hang blokkolása
+
+
+ Ajánlott
+
+
+ Hang és videó blokkolása
+
+
+ Tanulmányok
+
+
+ A Firefox időről időre tanulmányokat telepíthet és futtathat.
+
+
+ További tudnivalók
+
+
+ Az alkalmazás kilép a módosítások alkalmazásához
+
+
+ Eltávolítás
+
+
+ Aktív
+
+
+ Kész
+
+
+ Távoli hibakeresés USB-n/Wi-Fi-n
+
+
+ Feloldás
+
+
+ Erősítse meg az ujjlenyomatával
+
+
+ Ujjlenyomatával folytathatja az alkalmazás jelenlegi munkamenetét.
+
+
+ Hivatkozás megnyitása új munkamenetben
+
+
+ Ujjlenyomat ikon
+
+
+ Az ujjlenyomat nem ismerhető fel. Próbálja újra.
+
+
+ Az ujja túl gyorsan mozgott. Próbálja újra.
+
+
+ Keresési javaslatok megjelenítése?
+
+
+ A javaslatok kéréséhez a %1$snak el kell küldenie a címsorba írt üzenetet a keresőszolgáltatásnak.
+
+
+ Nem
+
+
+ Igen
+
+
+ Néhány keresőszolgáltatás nem tud javaslatokat megjeleníteni.
+
+
+ Eltüntetés
+
+
+
+
+ Az oldal váratlanul viselkedik?\nPróbálja kikapcsolni a követésvédelmet
+
+
+ Hozzáadás a kezdőképernyőhöz]]>
+
+
+ Nyisson meg minden hivatkozást a %1$sban\nÁllítsa be a %1$st alapértelmezett böngészőnek
+
+
+ URL-ek automatikus kiegészítése a leggyakrabban használt oldalaknál\nNyomja meg hosszan az URL-t a címsorban
+
+
+ Nyissa meg a hivatkozást új lapon\nNyomja meg hosszan bármelyik hivatkozást az oldalon
+
+
+ A tippek kikapcsolása a kezdőképernyőn
+
+
+ Új lap megnyitva
+
+
+ Átváltás
+
+
+ Belépés a teljes képernyős módba
+
+
+ Azonnali átváltás az új fülre
+
+
+ A potenciálisan veszélyes és megtévesztő oldalak blokkolása
+
+ A bejelentett megtévesztő, támadó szándékú, rosszindulatú és kéretlen szoftvereket terjesztő oldalak blokkolása.
+
+
+ Csak HTTPS mód
+
+
+ Automatikusan HTTPS titkosítási protokoll használatával próbál meg csatlakozni a webhelyekhez a fokozott biztonság érdekében.
+
+
+ Kivételek
+
+ Letiltotta a tartalomblokkolást ezeken az oldalakon.
+
+ Eltávolítás
+
+ Összes webhely eltávolítása
+
+
+ Sütik blokkolása
+
+
+ Szeretné blokkolni a sütiket?
+
+
+ A lap összeomlott
+
+ Sajnálom. Probléma van ezzel a lapon.
+
+ Privát böngészőként sosem mentjük el, és visszaállítani sem tudjuk ezt a lapot.
+
+ Lap bezárása
+
+
+
+
+
+ Összeomlási jelentése elküldése a Mozillának
+
+
+
+
+ Blokkolt nyomkövetők %s óta
+
+ Tartalom
+
+ Hirdetések
+
+ Közösségi média
+
+ Analitika
+
+ Fokozott követés elleni védelem
+
+ A védelem KI van kapcsolva ezen a webhelyen
+
+ A védelem BE van kapcsolva ezen a webhelyen
+
+ A kapcsolat biztonságos
+
+ A kapcsolat nem biztonságos
+
+ Blokkolandó követők és parancsfájlok
+
+
+ Ugrás vissza
+
+
+
+ Eltávolítás
+
+
+ Átnevezés
+
+ Átnevezés
+
+
+ Parancsikon neve
+
+
+ A mentett és megosztott képek <b>nem kerülnek</b> törlésre, ha törli a %1$s előzményeit.
+
+
+
+ Téma
+
+ Világos
+
+ Sötét
+
+ Energiagazdálkodás által beállítva
+
+ Az eszköz témájának követése
+
+
+
+ Ez a webhely nem támogatja a HTTPS-t
+
+
+ További tudnivalók
+ Ezt a beállítást itt módosíthatja: Beállítások > Adatvédelem és biztonság > Biztonság.]]>
+
+
+ A kapcsolat nem biztonságos
+
+
+
+ Ha korábban már sikeresen kapcsolódott ehhez a kiszolgálóhoz, akkor lehet, hogy a hiba csak ideiglenes.
+ ]]>
+
+
+ Lehet, hogy valaki megpróbálja megszemélyesíteni az oldalt, és a folytatás kockázatos lehet.
+
+ A %1$s nem bízik a(z) %2$s oldalban, mert a tanúsítvány kibocsátója ismeretlen, a tanúsítvány önaláírt, vagy a kiszolgáló nem küldi el a helyes közbülső tanúsítványokat.
+ ]]>
+
+
+
+ Lap bezárása
+
+
+
+ Megvannak! Megakadályoztuk, hogy ez a webhely kémkedjen Ön után. Koppintson a pajzsra, hogy megtudja mit blokkoltunk.
+
+
+ Felugró ablak bezárása
+
+
+
+ Védve van!
+
+ Ezek az alapértelmezett beállítások erős védelmet nyújtanak. De könnyű a beállításokat az Ön egyedi igényei szerint módosítani.
+
+ Eltüntetés
+
+
+ Koppintson ide, hogy mindent töröljön – az előzményeket, a sütiket, mindent –, és tiszta lappal induljon.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Bezárás
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Keresési modul
+
+ !-- This is the title of promote search widget dialog. -->
+
+ A böngészési előzmények törölve! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Indítsa el a privát böngészési munkamenetet, és menet közben blokkoljuk a nyomkövetőket és az egyéb rossz dolgokat.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Hagyjuk, hogy elkezdje a privát böngészést, de legközelebb gyorsabban nekiláthat a kezdőképernyőn található %1$s modullal.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Modul hozzáadása a kezdőképernyőhöz
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Modul hozzáadva a kezdőképernyőhöz
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-hus/strings.xml b/mobile/android/focus-android/app/src/main/res/values-hus/strings.xml
new file mode 100644
index 0000000000..c06004d46f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-hus/strings.xml
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+ Ka kuba\'
+ Ka bats\'uw
+
+ Ka dheya\'
+
+ Ka dhucha\' axi a le\' ka aliy
+
+ Eyendhanél abal kit almáts tsinat. \nKit almáts. Ka pakuw. Ka tsab t\'aja\'.
+
+ Pakuwatits an dhuchlab xi in olnál jant\'oj a aliyamal.
+
+ Pakuwatits an dhuchlab xi in olnál jant\'oj a aliyamal.
+
+ Ka aliy %1$s
+
+ Ka buk\'uw…
+
+ Ka olna\' max wa\'ats jún i odheltaláb al an xeklek
+
+ Ka japiy ti %1$s
+
+ Ka japiy ti…
+
+ Ka punuw al an ok\'ox xeklek
+
+ Ka t\'ojojoy
+ Tin kwenta an
+ Tolmixtaláb
+ A awiltal
+
+ Ka mapchij in belil an alim
+
+ Tolmidh k\'al %1$s
+
+ Ka buk\'uw ti
+
+ Ka pakuw an dhuchlab xi in olnál jant\'oj a aliyamal
+
+ Ka japiy
+
+ Ka pakuw ani ka japiy
+
+ Ka pakuw
+
+ Ka pakuw an dhuchlab xi in olnál jant\'oj a aliyamal
+
+
+ Ka pakuw ani ka japiy %1$s
+
+ Tsinataláb ani belkadhtaláb
+
+ Alim, cookies, a k\'al
+
+ An eyendhanél axi a takuyamal, kwetem kidhbax
+
+ Tin kwentaj %1$s, tolmixtaláb
+
+ Xi in kwa\'al an xeklek
+
+ Jalk\'unal an eyendhanél
+
+ Tin patál
+
+ Axi in penál a k\'al ani jant\'ini\' tu eynal
+
+ Ka aliy
+
+ Ti ucha\' jant\'oj axi a le\' ka aliy
+ %1$s ne\'ets kin abna\' patal axi a dhuchál ti al an alimtaláb
+
+ Axi punudh ok\'ox
+
+ Ka lek\'wtsiy
+
+ Ka tepdha\'
+
+ Ka kwetém kidhban an URL
+
+ + Ka punuw an URL axi tsab bijidh
+
+ Ka punuw an URL axi tsab bijidh
+
+ Ka punuw an URL axi tsab bijidh
+
+ Cookies ani xi dhuchadh ti xeklek
+
+ Eyendhanel tin kwentaj an xeklek
+
+ Ka pakuw an URL axi tsa bijidh
+
+ Ka exla\' más
+
+ Ka punuw ani ka t\'ojojoy an kwetém kidhbax URL.
+
+ URL axi ne\'ets ka punuw
+
+ Ka ts\'ata\' o ka dhucha\' an URL
+
+ Ejtil: mozilla.org
+
+ Ejtil: example.com
+
+ It URL axi it bijidh, punudh.
+
+ Ka pakuw
+
+ Ka pakuw
+
+ Ka dhucha\' juníl an URL axi a dhucha\'its ok\'ox.
+
+ Káwintaláb
+ Jant\'ini\' ok\'ox punudh an sistema
+
+ Tsinataláb
+ Yab ka jila\' kit olchin axi nujuwáb
+ Wa\'ats axi in olnál an nujnél, ani in tsu\'tal jant\'oj a met\'al al an xeklek axi a tsa\'biyal, aba\' ij ka ni\'a\' eblim al an olchixtaláb
+ Ka kuba\' an alim axi u tsalpax
+ Eynal abal kin tamkuy, kin met\'a\' ani kin lejbay an t\'ajbiláb, ejtil ka ni\'a\' ani ka tadhiy
+ Ka kuba\' an alim in k\'al an buk\'ul káwchik
+ Punudh al an xeklek abal ka exláj jawa\' a tsa\'biyamal ani in tejwa\'medhal ejtil an ni\'omtaláb ka buk\'uw
+ Ka kuba\' an alim xi in aliyal jant\'o in kwa\'al an xeklek
+ Max ka lek\'wtsiy, wa\'ats i xeklek axi yab ne\'ets ka alwa\' tejwa\'mej
+ Ka kuba\' an cookies
+
+ Ka kuba\' an cookies xi in k\'al pil jita\'
+ Ka kuba\' an cookies xi in k\'al pil jita\'
+
+ Ka eyendha\' in t\'iplabil a ch\'uchub abal ka japiy a eyextal
+
+ Yab tejwa\'
+ Ka tsina\' an xeklek tam k\'wajat a jalk\'uyal an eyendhanél.
+
+ k\'anidhtaláb
+
+ Alwa\'taláb
+ Ka kuba\' an fuentes xi ti xeklek
+ U awil ka k\'ibej anwalekláb ani an k\'ot\'biláb
+
+ Ka kuba\' an JavaScript
+ An xeklek u awil ka dhubath tejwa\'mej, poj u awil yab ka tejwa\'mej alwa\'
+
+ Ka t\'aja\' %1$s ta eyendhanelil
+
+ Mozilla
+ Ka ebna\' jay i eyendhanél a eyendhamal
+
+ Ka exla\' más
+
+ Mozilla in t\'ajál abal kin pena\' expidh xi in yejenchal ani kin alwa\'medha\' %1$s abal patal.
+
+ Tin kwentaj an tsinaxtaláb
+
+ Tin kwentaj an %1$s
+
+ An alimchik punudhits ta k\'al
+
+ Ka punuw juníl an alimchik axi talakits ok\'chidh
+
+ + Ka punuw pil i alim
+ Ka pakuw an alimchik
+ Ka pakuw
+
+ Ka punuw an alim
+
+ In bij an alim
+ Ka aliy an kawchik axi eynal
+ Dheya\'
+
+ Ejtil: example.com/search/?q=%s
+
+ Punuwat jun i it alim
+
+ Ka dhucha\' in bij an it alim
+ Jun i alim in kwa\'alits jayetsej nix\'e\' xi bijláb
+
+ Ka punuw axi ne\'ets ka aliy
+
+ Ka t\'aja\' ti kwentaj max axi a le\' ka dhucha\' ejtil an t\'iplab
+
+ Ka pakuw axi a dhuchamal
+
+ Yab ka t\'aja\' ti kwentaj
+
+ Ka pakuw an dhuchlab xi in olnál jant\'oj a aliyamal
+
+ Xeklekchik xi japidh: %1$s
+
+ Paladh alwa\' ani k\'anidh
+
+ K\'wajat ti t\'uchel
+
+ Tejwa\'its patal an xeklek
+
+ Eyendhanélchik
+
+ Kit k\'alej abal ok\'ox
+
+ Ka tsab pa\'baj an xeklek
+
+ Kit wichiy ju\'taj a tsa\'biyamalits
+
+ Ka jila\' ka pa\'iy an xeklek
+
+ Kit wichiy al xi jún an eyendhanél
+
+ "Jáy i alim kubadh "
+
+ Ka kuba\' an alimchik
+
+ A awiltal
+
+ Ka japiy al pil i eyendhanél
+ U awil kit kalej ti %1$s abal ka japiy ti %2$s.
+ Ka aliy jun i eyendhanel axi kin ejtow kin japiy
+ Ma ni jun i eyendhanél axi in kwa\'al a pat\'alil in jetowal kin japiy. U awil kit kalej ti %1$s abal ka aliy pil i eyendhanél abal %2$s.
+ ¿A le\' kit kalej ti tsinat tsa\'bixtaláb?
+
+ %1$s talbedhamej
+
+ Ka japiy
+
+
+
+
+
+
+
+
+
+ Yab elan an pat\'ál ju\'taj dheyach an xeklek
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ka k\'adhbanchij in tsapik an tsinataláb
+ Ka ne\'dha\' ebál an tsinat tsa\'bixtaláb. Ka kuba\' an olnom ani pilchik i tejwa\'medhomtaláb axi u awil ti tsinat met\'a\' al an xeklek, ani kin t\'aja\' abal an xeklekchik yab ka dhubat tejwa\'mej.
+
+ A alimtal jant\'in tat a le\'
+ ¿A aliyal jant\'oj pil? Ka t\'ojojoy pil i alim.
+
+ Ka punuw tin ok\'ox wal a pat\'alil, an bél xi kin taja\' kit ulits dhubat
+ Kit wichiy dhubat al an xeklek xi a kulbetnál ti %1$s. Ka takuy \"Ka punuw ti ok\'ox xeklek\" o ti %1$s.
+
+ Kit xe\'tsin k\'al an tsinataláb
+ Ka t\'aja\' abal %1$s ka wenk\'on ta alimtal ani ka ko\'oy an alwa\'taláb abal kit tsa\'bixin tsinat tam ka japiy jun i xeklek al pil i eyendhanél.
+
+ ¡U ejtiyal!
+ Yab ka t\'aja\' ti kwentaj
+ Xi tal
+
+ -
+
+ Ka punuw
+
+ Ka kuba\'
+
+ Axe\' an tsa\'bixtaláb tsinat
+
+ An tejwa\'medhomtaláb in jilál ka pakuw an t\'ojláb xi %1$s k\'al jún i takixtaláb. Yab in yejendhal ka japiy an eyendhanél o ka met\'a\' max k\'waja ti t\'ojnal al an tsa\'bixtaláb.
+
+ Ka pakuw an dhuchlab xi in olnál jant\'oj a aliyamal
+
+ Ka pa\'ba\' Firefox
+
+ %1$s pel jún i eyendhanél japidh, ts\'ejkadh k\'al Mozilla ani k\'al pilchik i junkudh t\'ojnal.
+
+
+
+
+ In bij an eyendhom
+ Tsinat japixtal
+ Ka k\'wit\'iy
+
+ Palat beletnadh
+ Palat po yab beletnadh
+ Met\'adh k\'al: %1$s
+
+ In k\'anix an xeklekchik
+ An URL wa\'tits
+
+ Ka aliy al an xeklek
+
+ Ka aliy al an xeklek
+
+ %1$d/%2$d
+ %1$d in k\'al %2$d
+
+ Ka met\'a\' axi ne\'ets ka tejwa\'mej talbél
+ Ka met\'a\' axoi kalej ok\'ox
+ Ka mapuy an alim
+
+
+
+ Ka konoy abal ka eyendha\' ta mexajil
+
+ URL k\'ot\'bidh
+
+ Eyendhanél abal an ts\'ejkom
+
+ Axi lej ebál in tsap
+
+ An kw\'it\'ixtaláb ma ti USB/Wi-Fi
+
+ T\'ipodhtaláb in k\'al a ch\'uchub
+
+ In t\'iplabil a ch\'uchub yab exláj. Ka exa\' juníl
+
+ A ch\'uchub ejláts tekedh adhik. Ka exa\' juníl.
+
+ Ibaj
+
+ Aníts
+
+ Wa\'ats i alimtaláb axi yab in olnál jant\'oj u awil ka aliy.
+
+ Yab ka t\'aja\' ti kwentaj
+
+
+
+ An xeklek yab u alwa\' t\'ojnal \n Ka exa\' ka kuba\' an k\'anix abal yab kit kedhmayat
+
+ Ka ela\' k\'al jun i takaxtaláb an xeklek axi a lej eyendhál%1$s Takuxtaláb> Ka punuw ti ok\'ox xeklek
+
+ Ka japiy patal an ne\'dhom %1$s Ka t\'aja\' %1$s ta ok\'ox eyendhom
+
+ "Ka eyendha\' an kweten putundhach URLs abal an xeklek axi a eyendal\n Ka ni\'a\' jawakits tum URLs ti alimtaláb\n "
+
+ Ka japiy jun i ne\'dhach al jun i it xeklek\n Ka jila\' ni\'adh jawakitsk\'ij tum ne\'dhach al jun i xeklek
+
+ Ka tepdha\' an kawchix axi ti olchal ti ok\'ox xeklek
+
+ Ka japiy al jún i it xeklek
+
+ Ka pakuw
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-hy-rAM/strings.xml b/mobile/android/focus-android/app/src/main/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000000..180b0c57ce
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-hy-rAM/strings.xml
@@ -0,0 +1,634 @@
+
+
+
+
+
+
+
+
+ Չեղարկել
+
+ Լավ
+
+ Պահպանել
+
+
+ Որոնեք կամ մուտքագրեք հասցե
+
+ Ինքնաշխատ գաղտնի դիտարկում: \nԴիտարկել: Ջնջել: Կրկնել:
+
+
+ Դիտարկումների պատմությունը ջնջվել է:
+
+
+ Որոնել %1$s-ի համար
+
+
+ Համօգտագործել
+
+
+ Հաղորդել կայքի խնդրի մասին
+
+
+ Բացել %1$s-ում
+
+
+ Բացել հետևյալում՝
+
+
+ Ավելացնել Տուն էկրանին
+
+
+ Հավելել դյուրանցումներին
+
+ Հանել դյուրանցումներից
+
+
+ Կարգավորումներ
+ Մասին
+ Օգնություն
+ Ձեր իրավունքները
+
+
+ Արգել. հետագծումներ
+
+
+ Սա անջատելով կարող են լուծվել կայքի որոշ խնդիրներ
+
+
+ %1$s-ի կողմից
+
+
+ Համօգտագործել՝
+
+ Ջնջել դիտարկումների պատմությունը
+
+
+ Բացել
+
+
+ Ջնջել ու Բացել
+
+
+ Ջնջել
+
+
+ Ջնջել դիտարկման պատմությունը
+
+
+
+ Որոնել Focus-ում
+
+
+ Գաղտնիություն և անվտանգություն
+
+
+ Վեբ բովանդակություն
+
+
+ Հավելվածների փոխարկում
+
+
+ Հիմնական
+
+ Որոնում
+
+
+ Լռելյայն
+
+
+ Որոնիչ
+
+
+ Միաց.
+
+
+ Անջ.
+
+
+ URL ինքնալրացում
+
+
+ Ձեր ավելացրած կայքերի համար
+
+
+ Կառավարել կայքերը
+
+
+ Կառավարել կայքերը
+
+
+ + Հավելել հարմարեցված URL
+
+
+ Ձեր ինքնալրացման ցուցակը.
+
+
+ Ավելացնել URL
+
+
+ Հավելել հարմարեցված URL
+
+
+ Ավելացնել հարմարեցված URL
+
+
+ ՀԵռացնել հարմարեցված URL-ները
+
+
+ Իմանալ ավելին
+
+
+ Ավելացնել և կառավարել հարմարեցված ինքնալրացվող URL-ները:
+
+
+ URL՝ ավելացնելու համար
+
+
+ Փակցրեք կամ մուտքագրեք URL
+
+
+ Օրինակ՝ mozilla.org
+
+
+ Օրինակ՝ example.com
+
+
+ Նոր հարմարեցված URL է ավելացվել:
+
+
+ Հեռացնել
+
+
+ Հեռացնել
+
+
+ Ստուգեք մուտքագրված URL-ն:
+
+ Լեզուն
+
+ Համակարգայինը
+
+ Գաղտնիություն
+ Արգելափակել գովազդային հետագծումները
+ Որոշ գովազդներ հետևում են կայքի այցելուներին, եթե անգամ չեք սեղմում գովազդին
+ Արգելափակել վերլուծական հետագծումները
+ Օգտագործվում է հավաքելու, վերլուծելու և ակտիվությունը չափելու համար, ինչպես օրինակ՝ հպումը և գլորումը:
+ Արգելափակել սոցիալական հետագծումները
+ Ներկառուցված կայքերում՝ հետևելուհ ամար ձեր այցելուներին և ցուցադրելու համար գործառությունը, ինչպես օրինակ՝ կոճակների համօգտագործում
+ Արգելափակել այլ բովանդակության հետագծումները
+ Միացնելու դեպքում որոշ էջեր կարող են անսպասելի վարք ցուցադրել
+ "Արգելափակել cookie-ները "
+
+
+ Ոչ, շնորհակալ եմ
+ Արգելափակել 3-րդ կողմի cookie-ները հիմա
+
+ Այո, խնդրում եմ
+
+
+ Թաքնված
+
+ Թաքցնել վեբ էջերը հավելվածները փոխանջատելիս
+
+ Անվտանգություն
+
+ Արդյունավետություն
+ Արգելափակել վեբ տառատեսակները
+
+ Կարող է հանգեցնել պատկերակների և պատկերների բացակայության
+
+ Արգելափակել JavaScript-ը
+
+ Էջերը կարող են ավելի արագ բեռնվել, բայց նաև կարող են իրենց անսպասելի պահել:
+
+
+ Դարձնել %1$s-ը հիմնական դիտարկիչ
+
+ Mozilla
+ Ուղարկել օգտագործման տվյալները
+
+
+ Իմանալ ավելին
+
+
+ Mozilla-ն ձգտում է հավաքել միայն այն, ինչ պետք է՝ %1$s-ը լավարկելու համար:
+
+
+ Գաղտնիության ծանուցում
+
+
+ Արտոնագրի տեղեկություն
+
+
+ %1$s-ի մասին
+
+
+ Տեղադրված որոնիչներ
+
+
+ Վերականգնել լռելյայն որոնիչները
+
+
+ + Ավելացնել այլ որոնիչ
+ Հեռացնել որոնիչները
+ Հեռացնել
+
+
+ Ավելացնել այլ որոնիչ
+
+
+ Ավելացնել որոնիչ
+
+ Որոնել որոնիչ
+ Որոնել տողը՝ օգտագործելու համար
+ Պահպանել
+
+
+ Օրինակ՝ example.com/search/?q=%s
+
+ Նոր որոնիչը ավելացվել է:
+
+ Մուտքագրեք որոնիչի անունը
+ Տեղադրված որոնիչը արդեն օգտագործում է այդ անունը:
+
+ Մուտքագրել որոնման տողը
+
+ Ստուգեք, որոնման տողը համապատասխանում է օրինակի ձևաչափին
+
+
+ Մաքրել ներածումը
+
+
+ Բաց թողնել
+
+
+ Ջնջել դիտարկման պատմությունը
+
+
+ Բաց ներդիրներ. %1$s
+
+
+ Անվտանգ կապակցում
+
+
+ Բեռնում
+
+
+ Կայքը բեռնվել է
+
+
+ Լր. ընտրանքներ
+
+
+ Ուղղորդել առաջ
+
+
+ Կրկին բեռնել կայքը
+
+
+ Ուղղորդել հետ
+
+
+ Կանգնեցնել էջի բեռնումը
+
+
+ Վերադառնալ նախորդ հավելվածին
+
+
+ Արգելափակել հետագծումները
+
+ Ձեր իրավունքները
+
+ Բացել հղումը այլ հավելվածով
+
+ Կարող եք բաց պահել %1$s-ը և բացել այս հղումը %2$s-ում:
+
+ Գտեք հավելված, որը կարող է բացել հղումը
+
+ Ձեր սարքի ոչ մի հավելված չի կարողանում բացել այս հղումը: Դուք կարող եք թողնել %1$s-ը՝ որոնելու համար %2$s-ում այն հավելվածը, որը կարող է այն բացել:
+
+ Փակե՞լ Գաղտնի դիտարկումը
+
+
+ %1$s ավարտվել է
+
+
+ Բացել
+
+
+
+
+
+
+
+
+
+ Սպասարկիչը չի գտնվել
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Փակել
+
+
+ Բաց թողնել
+
+
+ Հզորացրեք ձեր գաղտնիությունը
+
+ Տարեք գաղտնի դիտարկումը հաջորդ մակարդակ: Արգելափակեք գովազդները և այլ բովանդակություն, որը կարող է հետագծել ձեզ կայքերում և արագացրեք էջի բեռնումը:
+
+
+ Ձեր որոնումը, ձեր ընտրությունը
+
+ Այլ բա՞ն եք փնտրում: Ընտրեք այլ որոնիչ Կարգավորումներում:
+
+
+ Հավելել դյուրանցումները տուն էկրանին:
+
+ Արագորեն վերադարձեք ձեր ընտրյալ կայքերին %1$s-ում: Պարզապես ընտեք\"Հավելել Տուն էկրանին\" %1$s ցանկից:
+
+
+ Գայղտնիությունը դարձրեք սովորույթ
+
+ Կայեք %1$s-ը որպես հիմնական դիտարկիչ և ստացեք գաղտնի դիտարկման բոլոր առավելությունները՝ այլ հավելվածներից վեբ էջեր բացելիս:
+
+ Լավ, հասկացա:
+ Բաց թողնել
+ Հաջորդը
+
+
+ -
+
+
+ Ավելացնել
+
+
+ ԱՅՈ
+
+
+ Չեղարկել
+
+
+ ՈՉ
+
+
+ Գանղտնի դիտարկման աշխատաշրջան
+
+
+ Ծանուցումները հնարավորություն են տալիս ջնջել ձեր %1$s աշխատարջանը մեկ հպումով: Կարիք չկա բացել հավելվածը կամ տեսնել, թե ինչ է աշխատեցվում դիտարկիչում:
+
+
+ Ջնջել դիտարկման պատմությունը
+
+
+ Ներբեռնել Firefox-ը
+
+
+
+
+
+
+
+
+ Օգտվողի անուն
+ Գաղտնաբառ
+ Մաքրել
+
+
+
+ Անվտանգ կապակցում
+ Ոչ անվտանգ կապակցում
+
+ Ստուգված է՝ %1$s
+
+
+ Կայքի անվտանգությունը
+ URL-ն արդեն գոյություն ունի
+
+
+ Գտնել էջում
+
+
+ Գտնել էջում
+
+
+ Գտնել հաջորդ արդյունքը
+
+ Գտնել նախորդ արդյունքը
+
+
+ Պահանջել աշխատասեղանային կայքը
+
+
+ Աշխատասեղանի տարբերակ
+
+
+ URL-ն պատճենվեց
+
+
+ Մշակողի գործիքներ
+
+
+ Բացել հղումները հավելվածներում
+
+
+ Ընդլայնված
+
+
+ Կայքի թույլտվություններ
+
+
+ Միաց.
+
+
+ Անջ.
+
+
+ Չեղարկել
+
+
+ կարգավորումներ
+
+
+ Ինքնանվագարկում
+
+
+ Անցնել կարգավորումներին
+
+
+ Տեսախցիկ
+
+
+ Խոսափող
+
+
+ Գտնվելու վայր
+
+
+ Ծանուցումներ
+
+
+ DRM-ով կառավարվող բովանդակություն
+
+
+ Խնդրել թույլտվություն
+
+
+ Արգելափակված
+
+
+ Թույլատրված
+
+
+ Արգելափակված Android-ի կողմից
+
+
+ Թույլատրել ձայնանյութը և տեսանյութը
+
+
+ Արգելափակել միայն ձայնանյութը
+
+
+ Խորհուրդ է տրվում
+
+
+ Արգելափակել ձայնանյութը և տեսանյութը
+
+
+ Իմանալ ավելին
+
+
+ Հեռացնել
+
+
+ Բացել հղումը նոր աշխատաշրջանում
+
+
+
+
+ Նոր ներդիրը բացվեց
+
+
+ Հեռացնել
+
+ Հեռացնել բոլոր վեբ-կայքերը
+
+
+
+
+ Բովանդակություն
+
+ Գովազդ
+
+
+
+ Հեռացնել
+
+ Վերանվանել
+
+ Վերանվանել
+
+
+
+ Ոճ
+
+ Լուսավոր
+
+ Մուգ
+
+ Հետևել սարքի ոճին
+
+
+
+ Փակել ներդիրը
+
+
+ Փակել ելնող պատուհանը
+
+
+ Բաց թողնել
+
+
+
+
+ Փակել
+
+
+ Ավելացրեք վիջեթ հիմնական էկրանին
+
+
+ Վիջեթն ավելացվել է հիմնական էկրանին
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ia/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ia/strings.xml
new file mode 100644
index 0000000000..a5b2feab06
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ia/strings.xml
@@ -0,0 +1,1123 @@
+
+
+
+
+
+
+
+
+ Cancellar
+
+ OK
+
+ Salvar
+
+
+ Insere un adresse o face un recerca
+
+ Navigation private automatic.\nNaviga. Elimina. Repete.
+
+
+ Tu chronologia de navigation ha essite eliminate.
+ Chronologia de navigation clarate
+
+
+ Le chronologia de navigation del scheda ha essite eliminate.
+
+
+ Cercar %1$s
+
+
+ Compartir…
+
+
+ Reportar problema del sito
+
+
+ Aperir in %1$s
+
+
+ Aperir in…
+
+
+ Adder al pagina initial
+
+
+ Adder al vias breve
+
+ Remover del vias breve
+
+
+ Parametros
+ A proposito
+ Adjuta
+ Tu derectos
+
+
+ Traciatores blocate
+
+
+ Disactivar isto pote remediar alicun problemas del sito
+
+
+ Blocage de contentos
+
+ Clauder pro remediar alcun sitos
+
+
+ Potentiate per %1$s
+
+
+ Compartir con
+
+ Eliminar le chronologia de navigation?
+
+ Tocca o clara iste aviso pro cancellar con securitate tu chronologia de navigation.
+
+
+ Tocca o glissa iste aviso pro cancellar con securitate tu chronologia de navigation.
+
+ Eliminar le chronologia de navigation
+
+
+ Aperir
+
+
+ Eliminar e aperir
+
+
+ Eliminar
+
+
+ Eliminar le chronologia de navigation
+
+
+
+ Eliminar e aperir
+
+
+ Eliminar e aperir %1$s
+
+
+
+ Cercar in Focus
+
+
+ Cercar in Klar
+
+ Cercar in Focus Beta
+
+ Cercar in Focus Nightly
+
+
+ %1$s te pone al commando.
+Usa lo como un navigator private:
+
+ Cerca e naviga directemente in le app
+ Bloca le traciatores (o actualisa le parametros pro permitter traciatores)
+ Eliminar pro deler le cookies e tamben le chronologia de recercas e de navigation
+
+
+%1$s es producite per Mozilla. Nostre mission es promover un Internet san e aperte.
+Pro saper plus
]]>
+
+
+ Confidentialitate & securitate
+
+
+ Traciamento, cookies, electiones de datos
+
+
+ Definir option base, autocompletion
+
+
+
+
+ A proposito de %1$s, adjuta
+
+
+ Protection antitraciamento reinfortiate
+
+
+ Contento de web
+
+
+ Navigation trans le applicationes
+
+
+ General
+
+
+ Navigator, lingua predefinite
+
+
+ Colligimento e uso de datos
+
+ Cercar
+
+
+ Obtener suggestiones de recerca
+
+ %1$s inviara a tu motor de recerca lo que tu scribe in le barra de adresse
+
+
+ Predefinite
+
+
+ Motor de recerca
+
+
+ Activar
+
+
+ Inactive
+
+
+ Auto-completamento del URL
+
+
+ Pro le sitos le plus visitate
+
+
+ Activa pro consentir a %s de auto-completar plus de 450 URLs popular in le barra de adresses.
+
+
+ Pro le sitos que tu adde
+
+
+ Activar pro que %s completa automaticamente tu URLs favorite.
+
+
+ Gerer le sitos
+
+
+ Gerer le sitos
+
+
+ + Adder URL personalisate
+
+
+ Tu lista auto-completate:
+
+
+ Adder URL
+
+
+ Adder URL personalisate
+
+
+ Adder URL personalisate
+
+
+ Adde ligamine pro autocompletar
+
+
+ Cookies e datos de sitos
+
+
+ Selectiones de datos
+
+
+ Remover URLs personalisate
+
+
+ Saper plus
+
+
+ Adde e gere le complementamento personalisate del URLs.
+
+
+ URL a adder
+
+
+ Colla o insere le URL
+
+
+ Exemplo: mozilla.org
+
+
+ Exemplo: example.com
+
+
+ Nove URL personalisate addite.
+
+
+ Remover
+
+
+ Remover
+
+
+ Clicca duple le URL que tu insereva.
+
+ Lingua
+
+ Valor predefinite per le systema
+
+ Confidentialitate
+ Blocar le traciatores de publicitate
+ Alcun publicitates tracia le visitas al sitos, anque si tu non los clicca
+ Blocar le traciatores analytic
+ Usate pro colliger, analysar e mensurar activitates como toccar e rolar
+ Blocar le traciatores social
+ Integrate al sitos pro traciar tu visitas e monstrar functionalitates como buttones de compartir
+ Blocar le altere traciatores de contento
+ Activar isto pote facer le paginas se comportar de maniera inexpectate
+ Blocar le cookies
+
+
+ No gratias
+ Blocar solo le cookies traciator de tertie partes
+ Bloca solo le cookies de tertie partes
+
+ Blocar le cookies inter sitos
+ Si per favor
+
+
+ Usa le impression digital pro disblocar le application
+
+
+ Disbloca per le dactylogramma si tu addeva le vias breve o quando un sito web jam es aperte in %s.
+
+
+ Modo furtive
+
+ Celar le paginas web quando on cambia de applicationes e blocar le prisa de instantaneos.
+
+ Securitate
+
+ Rendimento
+ Blocar le typos de character de web
+
+ Pote resultar in icones o imagines mancante
+
+ Blocar JavaScript
+
+ Le paginas pote cargar se plus rapidemente, mais pute alsi comportar se in maniera inattendite
+
+
+ Facer de %1$s tu navigator predefinite
+
+ Mozilla
+ Inviar datos de uso
+
+
+ Saper plus
+
+
+ Mozilla se effortia a colliger solmente le informationes necessari pro fornir e meliorar %1$s pro totes.
+
+
+ Notification de confidentialitate
+
+
+ Informationes de licentias
+
+
+ Bibliothecas que nos usa
+
+
+ %s | Bibliothecas OSS
+
+
+ A proposito de %1$s
+
+
+ Motores de recerca installate
+
+
+ Elige motor de recerca
+
+
+ Restaurar le motores de recerca predefinite
+
+
+ + Adder un altere motor de recerca
+ Remover le motores de recerca
+ Remover
+
+
+ Adde un altere motor de recerca
+
+ Elige tu motor preferite:
+
+
+ Adder le motor de recerca
+
+ Nomine del motor de recerca
+ Catena de recerca a usar
+ Salvar
+
+
+ Exemplo: example.com/search/?q=%s
+
+ Nove motor de recerca addite.
+
+ Insere le nomine del fila de recerca
+ Un motor de recerca installate usa jam ille nomine.
+
+ Insere un fila de characteres pro le recerca
+
+ Verifica que le fila de recerca concorda con le formato de exemplo
+
+
+ Vacuar le texto inserite
+
+
+ Dimitter
+
+
+ Elimina le chronologia de navigation
+
+
+ Schedas aperite: %1$s
+
+
+ Connexion secur
+
+
+ Cargante
+
+
+ Sito web cargate
+
+
+ Altere optiones
+
+
+ Button altere optiones
+
+
+ Naviga in avante
+
+
+ Recarga le sito web
+
+
+ Naviga a retro
+
+
+ Cessa le cargamento del sito web
+
+
+ Retorna al application previe
+
+
+ Numero de traciatores blocate
+
+
+ Bloca le traciatores
+
+ Tu derectos
+
+ Aperir le ligamine in un altere application
+
+ Tu pote exir de %1$s e aperir le ligamine in %2$s.
+
+ Cercar un application capace de aperir le ligamine
+
+ Nulle application de tu apparato es capace de aperir iste ligamine. Tu pote exir de %1$s pro cercar in %2$s un application capace de facer lo.
+
+ Exir del navigation private?
+
+
+ %1$s finite
+
+
+ Aperir
+
+
+
+
+
+
+
+
+
+
+ Addite a vias breve!
+
+ Servitor non trovate
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clauder
+
+
+
+ Benvenite a %1$s
+
+
+ Rapide. Private. Nulle distractiones.
+
+
+ Comenciar
+
+
+
+ %1$s non es como altere navigatores
+
+
+ Nos clara tu chronologia quando tu claude le app, pro un confidentialitate extra.
+
+
+
+ Rende %1$s tu predefinite pro proteger tu datos con cata ligamine tu aperi.
+
+
+ Eliger como navigator predefinite
+
+
+ Saltar
+
+
+
+ Reinfortia tu confidentialitate
+
+ Porta le navigation private al nivello superior. Bloca publicitates e altere contentos que pote traciar te per le sitos e relentar le tempore de carga del paginas.
+
+
+ Le recerca a tu gusto
+
+ Cerca tu qualcosa differente? Elige un altere motor de recerca in le parametros.
+
+
+ Adde accessos directe a tu schermo initial
+
+ Retorna a tu sitos favorite in %1$s rapidemente. Selige simplemente \"Adder a tu schermo initial\" ab le menu %1$s.
+
+
+ Transforma le confidentialitate in un habito
+
+ Defini %1$s como tu navigator predefinite e obtene le avantages del navigation private quando tu aperi le paginas web ab altere applicationes.
+
+ Certo, io comprende!
+ Ignorar
+ Avante
+
+
+ -
+
+
+ Adder
+
+
+ SI
+
+
+ Cancellar
+
+
+ NO
+
+
+ Le via breve se aperira con le Protection antitraciamento reinfortiate disactivate
+
+
+ Session de navigation private
+
+
+ Le notificationes te permitte eliminar tu session %1$s con un tocco. Il non es necessari aperir le application o vider lo que es executate in tu navigator.
+
+
+ Eliminar le chronologia de navigation
+
+
+ Discargar Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License e altere licentias de codice aperte.]]>
+
+
+ ci.]]>
+
+
+ licentias libere e de codice aperte.]]>
+
+
+ Licentia Public GNU v3, e es disponibile ci .]]>
+
+
+ Nomine del usator
+ Contrasigno
+ Vacuar
+
+
+
+ Connexion secur
+ Connexion non secur
+
+ Verificate per: %1$s
+
+
+ Securitate del sito
+ Le URL existe ja
+
+
+ Cercar in le pagina
+
+
+ Cercar in le pagina
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Trovar le resultato successive
+
+ Trovar le resultato previe
+
+ Dimitter le recerca in le pagina
+
+
+
+
+ Requestar sito de scriptorio
+
+
+ Sito de scriptorio
+
+
+ URL copiate
+
+
+ Instrumentos de developpator
+
+
+ Aperir ligamines in apps
+
+
+ Avantiate
+
+
+ Permissiones del sito
+
+
+ Reduction de banner pro le cookie
+
+
+ Active
+
+
+ Inactive
+
+
+ Reduction de banner pro le cookie
+
+
+ Vider minus banners per automaticamente rejectar requestas de cookies, quando possibile.
+
+ -->
+ Reduction de banner pro le cookie
+
+
+ Activar pro iste sito
+
+
+ Sito actualmente non supportate
+
+
+ Disactivar pro iste sito
+
+
+ Reduction de banner pro le cookie
+
+
+ Disactivar pro iste sito
+
+
+ Activar pro iste sito
+
+
+ Activar le reduction de banner pro cookie pro %1$s?
+
+
+ Disactivar le reduction de banner pro cookie pro %1$s?
+
+
+ %1$s clarara le cookies de iste sito e actualisara le pagina. Clarar tote le cookies pote clauder tu connexion o vacuar tu carrettos de compras.
+
+
+ %1$s pote tentar de rejectar automaticamente le requestas de cookies.
+
+
+ Iste sito non es actualmente supportate per le Reduction del bandieras pro cookies. Amarea vos requirer a vostre equipa de revider iste sito web e adder le supporto in le futuro?
+
+
+ Cancellar
+
+
+ Requesta supporto
+
+
+ Requesta supporto sito inviate.
+
+
+ Requesta supporto sito inviate.
+
+
+
+ %1$s proba a rejectar requestas de cookies pro dimitter enoiose bandieras pro cookies.\n\nGere le preferentias de bandieras pro cookies in %2$s.
+
+
+ parametros
+
+
+ Autoreproduction
+
+
+ Pro permitter lo:
+
+
+ 1. Va a Parametros de Android
+
+
+ Permissiones]]>
+
+
+ Ir al parametros
+
+
+ %1$s a Active]]>
+
+
+ Camera
+
+
+ Microphono
+
+
+ Position
+
+
+ Notification
+
+
+ Contento protegite per DRM
+
+
+ Demandar pro permitter
+
+
+ Blocate
+
+
+ Permittite
+
+
+ Blocate per Android
+
+
+ Permitter audio e video
+
+
+ Blocar solo audio
+
+
+ Recommendate
+
+
+ Blocar audio e video
+
+
+ Studios
+
+
+ Firefox pote installar e conducer studios de tempore in tempore.
+
+
+ Pro saper plus
+
+
+ Le application quitara pro applicar cambiamentos
+
+
+ Remover
+
+
+ Active
+
+
+ Completate
+
+
+ Correction de errores remote per USB/wifi
+
+
+ Disblocar
+
+
+ Confirma per tu dactylogramma
+
+
+ Tu pote usar tu dactylogramma pro continuar tu actual session del app.
+
+
+ Aperir le ligamine in un nove session
+
+
+ Icone del impression digital
+
+
+ Impression digital non recognoscite. Prova de novo.
+
+
+ Digito movite troppo rapide. Prova de novo.
+
+
+ Monstrar suggestiones de recerca?
+
+
+ Pro obtener suggestiones, %1$s debe inviar lo que tu scribe in le barra de adresses al motor de recerca.
+
+
+ No
+
+
+ Si
+
+
+ Alcun motores de recerca non pote monstrar suggestiones.
+
+
+ Dimitter
+
+
+
+
+ Se comporta le sito inexpectatemente?\n Prova a disactivar le Protection de traciamento
+
+
+ Adder al Pagina initial]]>
+
+
+ Aperir cata ligamine in %1$s\n Pone %1$s como navigator predefinite
+
+
+ Auto-completar URLs pro le sitos que tu usa plus\n Retene pulsate longemente ulle URL in le barra de adresse
+
+
+ Aperi le ligamine in un nove scheda\n Retene pulsate longemente ulle ligamine sur un pagina
+
+
+ Disactivar le suggestiones sur le pagina initial
+
+
+ Nove scheda aperite
+
+
+ Commutar
+
+
+ Entrante in modo plen schermo
+
+
+ Commutar a ligamine in nove scheda immediatemente
+
+
+ Blocar le sitos potentialmente periculose e fraudulente
+
+ Blocar le sitos reportate como fraudulente e que attacca, le sitos de malware e illos de software non desirate.
+
+
+ Modo solo HTTPS
+
+
+ Automaticamente tenta de connecter se al sitos per le protocollo de cryptation HTTPS pro major securitate.
+
+
+ Exceptiones
+
+ Tu ha disactivate le blocage de contentos pro iste sitos.
+
+ Remover
+
+ Remover tote le sitos web
+
+
+ Blocar le cookies
+
+
+ Vole tu blocar le cookies?
+
+
+ Scheda collabite
+
+ Nos regretta. Nos ha un problema con iste scheda.
+
+ Como navigator private, nos non salva jammais iste scheda e es impossibile restaurar lo.
+
+ Clauder le scheda
+
+
+
+
+
+ Inviar reporto de crash a Mozilla
+
+
+
+
+ Traciatores blocate desde %s
+
+ Contento
+
+ Publicitate
+
+ Social
+
+ Analyse datos
+
+ Protection antitraciamento reinfortiate
+
+ Protectiones es INACTIVE pro iste sito
+
+ Le protectiones es ACTIVE pro iste sito
+
+ Connexion secur
+
+ Le connexion non es secur
+
+
+ Traciatores e scripts a blocar
+
+
+ Retornar
+
+
+
+ Remover
+
+
+ Renominar
+
+ Renominar
+
+
+ Nomine via-breve
+
+
+ Le imagines salvate e compartite <b>non essera</b> delite quando tu eliminara le chronologia de %1$s.
+
+
+
+ Thema
+
+ Clar
+
+ Obscur
+
+ Predefinite per sparniator de batteria
+
+ Sequer le thema del apparato
+
+
+
+ Iste sito non supporta HTTPS
+
+
+ Pro saper plus
+ Cambia iste parametro in Parametros > Confidentialitate e securitate > Securitate.]]>
+
+
+ Connexion non secur
+
+
+
+ Si tu te ha connectite a iste servitor con successo in le passato, le error pote esser temporari.
+ ]]>
+
+
+ Il es possibile que alcuno tenta usurpar le identitate del sito e pote esser riscose continuar.
+
+ %1$s non considera digne de fide %2$s perque su emissor de certificatos es incognite, le certificato es auto-signate, o le servitor non invia le correcte certificatos intermedie.
+ ]]>
+
+
+
+ Clauder scheda
+
+
+
+ Sasite! Nos stoppava iste sito de spiar te. Quandocunque tocca le scuto pro vider lo que es blocate.
+
+
+ Clauder message emergente
+
+
+
+ Tu es protegite!
+
+ Iste parametros predefinite offere forte protection. Ma il es facile adjustar le parametros pro satisfacer tu specific besonios.
+
+ Ignorar
+
+
+ Tocca ci pro eliminar tote le datos, chronologia, cookies, toto, e recomenciar con un scheda tote nove.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Clauder
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Cercar widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Chronologia de navigation clarate! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Initia tu session de navigation private, nos blocara traciatores e altere menacias in tu route.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Nos te lassa a tu navigation private, ma memora le proxime vice que facilemente potera initiar con le widget %1$s sur tu Pagina initial.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Adder widget al pagina initial
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget addite al pagina initial
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-in/strings.xml b/mobile/android/focus-android/app/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000000..cab78af002
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-in/strings.xml
@@ -0,0 +1,1108 @@
+
+
+
+
+
+
+
+
+ Batal
+
+ Oke
+
+ Simpan
+
+
+ Cari atau masukkan alamat
+
+ Penjelajahan pribadi otomatis.\nJelajahi. Hapus. Ulangi.
+
+
+ Riwayat penjelajahan Anda sudah dihapus.
+ Riwayat penjelajahan dihapus
+
+
+ Riwayat penjelajahan tab telah dihapus.
+
+
+ Cari %1$s
+
+
+ Bagikan…
+
+
+ Laporkan Masalah Situs
+
+
+ Buka di %1$s
+
+
+ Buka di…
+
+
+ Tambahkan ke Beranda
+
+
+ Tambahkan ke Pintasan
+
+ Hapuskan dari Pintasan
+
+ Pengaturan
+ Tentang
+ Bantuan
+ Hak Anda
+
+
+ Pelacak terblokir
+
+
+ Menonaktifkan ini mungkin membantu memecahkan masalah beberapa situs
+
+
+ Pemblokiran Konten
+
+ Nonaktifkan untuk memecahkan masalah beberapa situs
+
+
+ Diberdayakan oleh %1$s
+
+
+ Bagikan via
+
+
+ Hapus riwayat penjelajahan
+
+
+ Buka
+
+
+ Hapus dan Buka
+
+
+ Hapus
+
+
+ Hapus riwayat penjelajahan
+
+
+
+ Hapus & buka
+
+
+ Hapus dan buka %1$s
+
+
+
+ Cari di Focus
+
+ Cari di Klar
+
+ Cari di Focus Beta
+
+ Cari di Focus Nightly
+
+
+ %1$s menempatkan Anda dalam kendali.
+Gunakan sebagai peramban privat:
+
+ Cari dan jelajah langsung di dalam aplikasi
+ Blokir pelacak (atau ubah pengaturan untuk izinkan pelacak)
+ Hapus kuki termasuk juga pencarian dan riwayat penjelajahan
+
+
+%1$s diproduksi oleh Mozilla. Misi kami adalah untuk memelihara Internet yang sehat dan terbuka.
+Pelajari lebih lanjut
]]>
+
+
+ Privasi & Keamanan
+
+
+ Pelacakan, kuki, pilihan data
+
+
+ Atur baku, lengkapi otomatis
+
+
+
+
+ Tentang %1$s, bantuan
+
+
+ Perlindungan Pelacakan Dipertingkat
+
+
+ Konten Web
+
+
+ Mengganti Aplikasi
+
+
+ Umum
+
+
+ Peramban, tab, dan bahasa baku
+
+
+ Pengumpulan Data & Penggunaan
+
+ Cari
+
+
+ Dapatkan saran pencarian
+
+ %1$s akan mengirim apa yang Anda ketik di bilah alamat ke mesin pencari Anda
+
+
+ Baku
+
+
+ Mesin pencari
+
+
+ Aktif
+
+
+ Nonaktif
+
+
+ Lengkapi-otomatis URL
+
+
+ Untuk Situs teratas
+
+
+ Aktifkan agar %s melengkapi otomatis lebih dari 450 URL populer di bilah alamat.
+
+
+ Untuk Situs yang Anda Tambahkan
+
+
+ Aktifkan %s melengkapi otomatis URL favorit Anda.
+
+
+ Kelola situs
+
+
+ Kelola situs
+
+
+ + Tambah URL ubahsuai
+
+
+ Daftar lengkapi-otomatis Anda:
+
+
+ Tambahkan URL
+
+
+ Tambah URL ubahsuai
+
+
+ Tambahkan URL kustom
+
+
+ Tambahkan tautan untuk dilengkapi secara otomatis
+
+
+ Kuki dan Data Situs
+
+
+ Pilihan Data
+
+
+ Hapus URLs ubahsuai
+
+
+ Pelajari lebih lanjut
+
+
+ Tambahkan dan kelola lengkapi-otomatis URL ubahsuai.
+
+
+ URL untuk ditambahkan
+
+
+ Tempel atau masukkan URL
+
+
+ Contoh: mozilla.org
+
+
+ Contoh: contoh.id
+
+
+ URL ubahsuai baru ditambahkan.
+
+
+ Hapus
+
+
+ Hapus
+
+
+ Periksa ulang URL yang Anda masukkan.
+
+ Bahasa
+
+ Bawaan sistem
+
+ Privasi
+ Blokir pelacak iklan
+ Beberapa iklan melacak kunjungan situs, meski Anda tidak mengklik iklan
+ Blokir pelacak analitik
+ Digunakan untuk mengumpulkan, menganalisis, dan mengukur kegiatan seperti mengetuk dan menggulir
+ Blokir pelacak sosial
+ Disematkan pada situs untuk melacak kunjungan Anda dan menampilkan fungsi seperti tombol berbagi
+ Blokir pelacak konten lainnya
+ Mengaktifkan fitur ini mungkin menyebabkan beberapa laman bertindak tidak terduga
+ Blokir kuki
+
+
+ Tidak, terima kasih
+ Hanya blokir kuki pelacak pihak ke-3
+ Hanya blokir kuki pihak ke-3
+
+ Blokir kuki lintas situs
+ Ya
+
+
+ Gunakan sidik jari untuk membuka aplikasi
+
+
+ Buka kunci dengan sidik jari jika Anda telah menambahkan Pintasan atau saat situs web sudah dibuka di %s.
+
+
+ Mode Siluman
+
+ Sembunyikan laman web saat berpindah aplikasi dan blokir tangkapan layar.
+
+ Keamanan
+
+ Kinerja
+ Blokir web fonts
+
+ Dapat menyebabkan hilangnya ikon atau gambar
+
+ Blokir JavaScript
+
+ Laman dapat dimuat lebih cepat, namun bisa juga bertindak tanpa diduga-duga
+
+
+ Jadikan %1$s peramban baku
+
+ Mozilla
+ Kirim data penggunaan
+
+
+ Pelajari lebih lanjut
+
+
+ Mozilla berusaha untuk mengumpulkan hanya apa yang kami perlu untuk sediakan dan meningkatkan %1$s untuk setiap orang.
+
+
+ Kebijakan Privasi
+
+
+ Informasi lisensi
+
+
+ Pustaka yang kami gunakan
+
+
+ %s | Pustaka OSS
+
+
+ Tentang %1$s
+
+
+ Mesin pencari terpasang
+
+
+ Pilih mesin pencari
+
+
+ Pulihkan mesin pencari baku
+
+
+ + Tambahkan mesin pencari lainnya
+ Hapus mesin pencari
+ Hapus
+
+
+ Tambahkan mesin pencari lainnya
+
+ Pilih mesin pilihan Anda:
+
+
+ Tambah mesin pencari
+
+ Nama mesin pencari
+ String pencarian untuk digunakan
+ Simpan
+
+
+ Contoh: contoh.id/search/?q=%s
+
+ Mesin pencari baru ditambahkan.
+
+ Masukkan nama mesin pencari
+ Mesin pencari yang terpasang telah menggunakan nama tersebut.
+
+ Masukkan string pencarian
+
+ Periksa apakah string pencarian cocok dengan format Contoh
+
+
+ Bersihkan masukan
+
+
+ Tutup
+
+
+ Hapus riwayat penjelajahan
+
+
+ Tab terbuka: %1$s
+
+
+ Sambungan aman
+
+
+ Memuat
+
+
+ Situs web dimuat
+
+
+ Opsi lainnya
+
+
+ Tombol opsi lainnya
+
+
+ Navigasi ke selanjutnya
+
+
+ Muat ulang situs web
+
+
+ Arahkan kembali
+
+
+ Berhenti memuat situs web
+
+
+ Kembali ke aplikasi sebelumnya
+
+
+ Jumlah pelacak terblokir
+
+
+ Blokir pelacak
+
+ Hak Anda
+
+ Buka tautan di aplikasi lainnya
+
+ Anda dapat meninggalkan %1$s untuk membuka tautan ini di %2$s.
+
+ Temukan aplikasi yang dapat membuka tautan
+
+ Tak ada satu pun aplikasi di perangkat Anda dapat membuka tautan ini. Anda dapat meninggalkan %1$s untuk mencari di %2$s untuk mendapatkan aplikasi yang mampu.
+
+ Keluar dari Penjelajahan Pribadi?
+
+
+ %1$s selesai
+
+
+ Buka
+
+
+
+
+
+
+
+
+
+
+ Ditambahkan ke pintasan!
+
+ Peladen tidak ditemukan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tutup
+
+
+
+ Selamat datang di %1$s
+
+
+ Cepat. Pribadi. Tanpa gangguan.
+
+
+ Memulai
+
+
+
+ %1$s tidak seperti peramban lain
+
+
+ Kami menghapus riwayat Anda saat menutup aplikasi untuk privasi tambahan.
+
+
+
+ Jadikan %1$s sebagai perlindungan baku data Anda dengan setiap tautan yang Anda buka.
+
+
+ Setel sebagai peramban baku
+
+
+ Lewati
+
+
+
+ Perkuat privasi Anda
+
+ Bawa penjelajahan pribadi ke tingkat berikutnya. Blokir iklan dan konten lainnya yang dapat melacak Anda di seluruh situs dan memperlambat waktu buka halaman.
+
+
+ Pencarian Anda, cara Anda
+
+ Mencari sesuatu yang berbeda? Pilih mesin pencari baku lainnya di Pengaturan.
+
+
+ Tambahkan pintasan ke layar Beranda
+
+ Kembali ke situs favorit Anda di %1$s dengan cepat. Cukup pilih \"Tambahkan ke layar Beranda\" dari menu %1$s.
+
+
+ Jadikan privasi menjadi kebiasaan
+
+ Atur %1$s sebagai peramban utama Anda dan dapatkan keuntungan penjelajahan pribadi ketika Anda membuka laman web dari aplikasi lainnya.
+
+ Oke, beres!
+ Lewati
+ Lanjutkan
+
+
+ -
+
+
+ Tambah
+
+
+ YA
+
+
+ Batal
+
+
+ TIDAK
+
+
+ Pintasan akan terbuka dengan Perlindungan Pelacakan Dipertingkat dinonaktifkan
+
+
+ Sesi penjelajahan pribadi
+
+
+ Notifikasi memungkinkan Anda menghapus sesi %1$s Anda dengan sekali sentuh. Anda tak perlu membuka aplikasi atau melihat apa yang berjalan di peramban Anda.
+
+
+ Hapus riwayat penjelajahan
+
+
+ Unduh Firefox
+
+
+
+
+
+
+
+
+ Lisensi Publik Mozilla dan lisensi sumber terbuka lainnya.]]>
+
+
+ di sini.]]>
+
+
+ lisensi bebas dan sumber terbuka lainnya.]]>
+
+
+ GNU General Public License v3, dan tersedia di sini .]]>
+
+
+ Nama pengguna
+ Sandi
+ Bersihkan
+
+
+
+ Sambungan Aman
+ Sambungan Tidak Aman
+
+ Diverifikasi oleh: %1$s
+
+
+ Keamanan Situs
+ URL sudah tersedia
+
+
+ Temukan di Laman
+
+
+ Temukan di laman
+
+
+ %1$d/%2$d
+
+ %1$d dari %2$d
+
+
+ Temukan hasil selanjutnya
+
+ Temukan hasil sebelumnya
+
+ Tutup temukan di laman
+
+
+
+
+ Minta situs desktop
+
+
+ Situs desktop
+
+
+ URL tersalin
+
+
+ Alat pengembang
+
+
+ Buka tautan di aplikasi
+
+
+ Lanjutan
+
+
+ Izin situs
+
+
+ Pengurangan Spanduk Kuki
+
+
+ Aktif
+
+
+ Mati
+
+
+ Pengurangan Spanduk Kuki
+
+
+ Lihat lebih sedikit spanduk dengan menolak permintaan kuki secara otomatis, jika memungkinkan.
+
+ -->
+ Pengurangan Spanduk Kuki
+
+
+ AKTIF untuk situs ini
+
+
+ Saat ini, situs tidak didukung
+
+
+ NONAKTIF untuk situs ini
+
+
+ Pengurangan Spanduk Kuki
+
+
+ NONAKTIF untuk situs ini
+
+
+ AKTIF untuk situs ini
+
+
+ Aktifkan Pengurangan Spanduk Kuki untuk %1$s?
+
+
+ Nonaktifkan Pengurangan Spanduk Kuki untuk %1$s?
+
+
+ %1$s akan menghapus kuki situs ini dan menyegarkan laman ini. Membersihkan semua kuki dapat membuat Anda keluar dari suatu situs atau mengosongkan keranjang belanja.
+
+
+ %1$s dapat mencoba menolak permintaan kuki secara otomatis.
+
+
+ Situs ini saat ini tidak didukung oleh Fitur Pengurangan Spanduk Kuki. Ingin meminta tim kami meninjau situs ini dan menambahkan dukungan di masa mendatang?
+
+
+ Batal
+
+
+ Minta dukungan
+
+
+ Permintaan bantuan situs telah diajukan.
+
+
+ Permintaan bantuan situs telah diajukan.
+
+
+
+ %1$s mencoba menolak permintaan kuki untuk menghentikan spanduk kuki yang menjengkelkan.\n\nKelola preferensi spanduk kuki di %2$s.
+
+ pengaturan
+
+
+ Putar Otomatis
+
+
+ Untuk mengizinkannya:
+
+
+ 1. Buka Pengaturan Android
+
+
+ Perizinan]]>
+
+
+ Buka Pengaturan
+
+
+ %1$s]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Lokasi
+
+
+ Pemberitahuan
+
+
+ Konten yang diatur DRM
+
+
+ Meminta izin
+
+
+ Diblokir
+
+
+ Diizinkan
+
+
+ Diblokir oleh Android
+
+
+ Izinkan suara dan video
+
+
+ Blokir suara saja
+
+
+ Disarankan
+
+
+ Blokir suara dan video
+
+
+ Kajian
+
+
+ Firefox dapat memasang dan menjalankan kajian dari waktu ke waktu.
+
+
+ Pelajari lebih lanjut
+
+
+ Aplikasi akan berhenti untuk menerapkan perubahan
+
+
+ Hapus
+
+
+ Aktif
+
+
+ Selesai
+
+
+ Pengawakutuan jarak jauh melalui USB/Wi-Fi
+
+
+ Buka
+
+
+ Konfirmasi Menggunakan Sidik Jari Anda
+
+
+ Anda dapat menggunakan sidik jari untuk melanjutkan sesi aplikasi Anda saat ini.
+
+
+ Buka Tautan di Sesi Baru
+
+
+ Ikon sidik jari
+
+
+ Sidik jari tidak dikenali. Coba lagi.
+
+
+ Jari bergerak terlalu cepat. Coba lagi.
+
+
+ Tampilkan saran pencarian?
+
+
+ Untuk mendapatkan saran, %1$s perlu mengirim apa yang Anda ketikkan di bilah alamat ke mesin pencari.
+
+
+ Tidak
+
+
+ Ya
+
+
+ Beberapa mesin pencari tidak dapat menampilkan saran pencarian.
+
+
+ Tutup
+
+
+
+
+ Situs bertindak tidak terduga?\n Matikan Perlindungan Pelacakan
+
+
+ Tambahkan ke Beranda]]>
+
+
+ Buka setiap tautan di %1$s\n Atur %1$s sebagai peramban baku
+
+
+ Lengkapi otomatis URL untuk situs yang sering dikunjungi\n Ketuk lama URL di bilah alamat
+
+
+ Buka tautan di tab baru\n Tekan lama tautan di laman
+
+
+ Nonaktifkan tips ini pada layar mulai
+
+
+ Tab baru dibuka
+
+
+ Tukar
+
+
+ Memasuki mode layar penuh
+
+
+ Alihkan tautan langsung ke tab baru
+
+
+ Blokir situs yang mungkin berbahaya serta penipuan
+
+ Blokir situs penipuan dan penyerangan yang dilaporkan, situs malware, dan situs perangkat lunak yang tidak diinginkan.
+
+
+ Mode Hanya HTTPS
+
+
+ Secara otomatis mencoba terhubung ke situs menggunakan protokol enkripsi HTTPS untuk meningkatkan keamanan.
+
+
+ Pengecualian
+
+ Anda telah menonaktifkan Pemblokiran Konten untuk situs ini.
+
+ Hapus
+
+ Hapus semua situs web
+
+
+ Blokir Kuki
+
+
+ Apakah Anda ingin memblokir kuki?
+
+
+ Tab Mogok
+
+ Maaf. Kami mengalami masalah dengan tab ini.
+
+ Sebagai peramban pribadi, kami tidak pernah menyimpan dan tidak dapat mengembalikan tab ini.
+
+ Tutup Tab
+
+
+
+
+
+ Kirim laporan kerusakan ke Mozilla
+
+
+
+
+ Pelacak diblokir sejak %s
+
+ Konten
+
+ Periklanan
+
+ Sosial
+
+ Analitis
+
+ Perlindungan Pelacakan Dipertingkat
+
+ Perlindungan DINONAKTIFKAN untuk situs ini
+
+ Perlindungan DIAKTIFKAN untuk situs ini
+
+ Sambungan aman
+
+ Sambungan tidak aman
+
+ Pelacak dan Skrip untuk Diblokir
+
+
+ Kembali
+
+
+
+ Hapus
+
+
+ Ubah Nama
+
+ Ubah Nama
+
+ Nama pintasan
+
+
+ Gambar yang disimpan dan dibagikan <b>tidak akan dihapus</b> ketika Anda menghapus riwayat %1$s.
+
+
+
+ Tema
+
+ Terang
+
+ Gelap
+
+ Diatur oleh Penghemat Baterai
+
+ Ikuti tema peranti
+
+
+ Laman ini tidak mendukung HTTPS
+
+
+ Pelajari lebih lanjut
+ Ubah setelan ini di Setelan > Privasi & Keamanan > Keamanan.]]>
+
+
+ Sambungan tidak aman
+
+
+ Jika Anda sebelumnya pernah tersambung dengan peladen ini, kesalahan ini mungkin hanya sementara.]]>
+
+
+ Seseorang mungkin berusaha menyamar menyamar laman ini dan sebaiknya Anda tidak melanjutkan.
+
+ %1$s tidak mempercayai %2$s karena penerbit sertifikat tidak diketahui, sertifikat ditandatangani sendiri, atau peladen tidak mengirimkan sertifikat perantara yang benar. ]]>
+
+
+
+ Tutup tab
+
+
+
+ Dapat! Kami menghentikan situs ini untuk memata-mataimu. Ketuk perisai kapan saja untuk melihat apa yang kami blokir.
+
+
+ Tutup popup
+
+
+
+ Anda terlindungi!
+
+ Pengaturan bawaan ini menawarkan perlindungan yang kuat. Tetapi mudah untuk mengubah pengaturan untuk memenuhi kebutuhan spesifik Anda.
+
+ Tutup
+
+
+ Ketuk di sini untuk membuang semuanya — riwayat, kuki, semuanya — dan memulai dari tab baru.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Tutup
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Cari widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Riwayat penjelajahan dihapus! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Mulai sesi penjelajahan pribadi Anda, dan kami akan memblokir pelacak dan hal buruk lainnya saat Anda menjelajah.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Kami akan meninggalkan Anda untuk menjelajah pribadi, tapi dapatkan awal yang lebih cepat lain kali dengan widget %1$s di layar Beranda Anda.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Tambahkan widget ke layar beranda
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget ditambahkan ke layar beranda
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-is/strings.xml b/mobile/android/focus-android/app/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000000..e6965d61ba
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-is/strings.xml
@@ -0,0 +1,1091 @@
+
+
+
+
+
+
+
+
+ Hætta við
+
+ Í lagi
+
+ Vista
+
+
+ Leitaðu eða settu inn vistfang
+
+ Sjálfvirkt huliðsvafur.\nVafrað. Eytt. Endurtekið.
+
+
+ Vafurferli þínu hefur verið eytt.
+ Vafurferill hreinsaður
+
+
+ Vafurferli flipans hefur verið eytt.
+
+
+ Leita að %1$s
+
+
+ Deila…
+
+
+
+ Tilkynna vandamál með vefsvæði
+
+
+ Opna með %1$s
+
+
+ Opna með…
+
+
+ Bæta við á ræsisíðu
+
+
+ Bæta við flýtileiðir
+
+ Fjarlægja úr flýtileiðum
+
+
+ Stillingar
+ Um hugbúnaðinn
+ Hjálp
+
+ Réttindi þín
+
+
+ Lokað á rekjara
+
+
+ Að slökkva á þessu gæti lagað sum vandamál á vefsvæðinu
+
+
+ Blokkun efnis
+
+
+ Slökktu á þessu til að laga sum vefsvæði
+
+
+ Keyrt með %1$s
+
+
+
+ Deila með
+
+ Eyða vafurferli?
+
+ Ýttu á eða hreinsaðu þessa tilkynningu til að eyða vafurferli þínum á öruggan hátt.
+
+
+ Ýttu á eða strjúktu út þessa tilkynningu til að eyða vafurferli þínum á öruggan hátt.
+
+ Eyða vafraferli
+
+
+ Opna
+
+
+ Eyða og opna
+
+
+ Hreinsa
+
+
+ Eyða vafraferli
+
+
+
+ Hreinsa og opna
+
+
+ Hreinsa og opna %1$s
+
+
+ Leita í Focus
+
+ Leita í Klar
+
+ Leita í Focus Beta
+
+ Leita í Focus næturútgáfunni
+
+
+ %1$s setur þig við stjórnvölinn.
+Notaðu það sem huliðsvafra:
+
+ Leitaðu og flettu beint í forritinu
+ Lokaðu á rekjara (eða uppfærðu stillingar til að leyfa rekjara)
+ Hreinsaðu til að eyða vefkökum sem og leitar- og vafraferli
+
+
+%1$s er framleitt af Mozilla. Markmið okkar er að hlúa að heilbrigðu, opnu interneti.
+Frekari upplýsingar
]]>
+
+
+ Friðhelgi og öryggi
+
+
+ Rekjarar, vefkökur, gagnaval
+
+
+ Setja sjálfgefið, sjálfvirk útfylling
+
+
+
+
+ Um %1$s, hjálp
+
+
+ Aukin rakningarvörn
+
+
+ Vefefni
+
+
+ Skipta um forrit
+
+
+ Almennt
+
+
+ Sjálfgefinn vafri, tungumál
+
+
+ Gagnasöfnun og notkunarupplýsingar
+
+ Leita
+
+
+ Fá leitartillögur
+
+ %1$s mun senda það sem þú skrifar í slóðina yfir á leitarvélina þína
+
+
+ Sjálfgefið
+
+
+ Leitarvél
+
+
+ Virkt
+
+
+ Óvirkt
+
+
+ Sjálfvirk leiðrétting á slóðum
+
+
+ Fyrir vinsælustu vefsvæðin
+
+
+ Virkja sjálfvirka útfyllingu í staðsetningarslá %s á rúmlega 450 vinsælum slóðum.
+
+
+ Fyrir vefsvæði sem þú bætir við
+
+
+ Virkja til að leyfa %s að klára uppáhaldsvefslóðirnar þínar.
+
+
+ Sýsla með vefsvæði
+
+
+ Sýsla með vefsvæði
+
+
+ + Bæta við sérsniðinni vefslóð
+
+
+ Sjálfvirki útfyllingarlistinn þinn:
+
+
+ Bæta við vefslóð
+
+
+ Bæta við sérsniðinni vefslóð
+
+
+ Bæta við sérsniðinni vefslóð
+
+
+ Bæta tengli við sjálfvirka útfyllingu
+
+
+ Smákökur og gögn vefsvæðis
+
+
+ Meðhöndlun gagna
+
+
+ Fjarlægja sérsniðnar vefslóðir
+
+
+ Frekari upplýsingar
+
+
+ Bættu við og stjórnaðu sérsniðnum sjálfvirkum útfyllingarslóðum.
+
+
+ URL-slóð til að bæta við
+
+
+ Límdu eða settu inn slóð
+
+
+ Dæmi: mozilla.org
+
+
+ Til dæmis: example.com
+
+
+ Nýrri sérsniðinni vefslóð bætt við.
+
+
+ Fjarlægja
+
+
+ Fjarlægja
+
+
+ Yfirfarðu slóðina sem þú settir inn.
+
+ Tungumál
+
+ Sjálfgefið í stýrikerfinu
+
+ Friðhelgi
+ Loka á auglýsingarekjara
+ Sumar auglýsingar geyma upplýsingar um vefsvæði sem hafa verið heimsótt, þótt sé smellt ekki á auglýsingarnar
+ Loka á greiningarekjara
+ Notað til að safna, greina og fylgjast með virkni, eins og að snerta og skruna
+ Lokar á samfélagmiðlarekjara
+ Innbyggt í vefsvæðum til að fylgjast með heimsóknum þínum og sýna virkni eins og deilihnappar
+ Loka á aðra efnisrekjara
+ Virkjun gæti valdið því að sum vefsvæði hagi sér á óvæntan hátt
+ Loka á vefkökur
+
+
+ Nei takk
+ Loka einungis á rakningarvefkökur frá utanaðkomandi aðilum
+ Loka einungis á vefkökur frá utanaðkomandi aðilum
+ Loka á milli-vefsvæða-vefkökur
+ Já endilega
+
+
+ Nota fingrafar til að aflæsa smáforriti
+
+
+ Aflæstu með fingrafari ef þú hefur bætt við flýtileiðum eða þegar vefsvæði er þegar opið í %s.
+
+
+ Laumuspil
+
+ Fela vefsíður þegar skipt er um forrit og loka á skjámyndatöku.
+
+ Öryggi
+
+ Afköst
+ Loka á vefleturgerðir
+
+
+ Getur valdið því að tákn eða myndir vantar
+
+ Loka á JavaScript
+
+
+ Síður kunna að hlaðast hraðar inn en geta líka hegðað sér á óvænta vegu
+
+
+ Gera %1$s að sjálfgefnum vafra
+
+ Mozilla
+ Senda notkunarupplýsingar
+
+
+ Frekari upplýsingar
+
+
+ Mozilla safnar eingöngu því sem nauðsynlegt er til að betrumbæta %1$s fyrir alla.
+
+
+ Upplýsingar um meðferð persónuupplýsinga
+
+
+ Upplýsingar um notkunarleyfi
+
+
+ Aðgerðasöfn sem við notum
+
+
+ %s | OSS-aðgerðasöfn
+
+
+ Um %1$s
+
+
+ Uppsettar leitarvélar
+
+
+ Veldu leitarvél
+
+
+ Endurheimta sjálfgefnar leitarvélar
+
+
+ + Bæta við annari leitarvél
+ Fjarlægja leitarvélar
+ Fjarlægja
+
+ Bæta við annari leitarvél
+
+ Veldu leitarvélina sem þú kýst:
+
+
+ Bæta við leitarvél
+
+ Heiti leitarvélar
+ Leitarstrengur sem á að nota
+ Vista
+
+
+ Dæmi: example.com/search/?q=%s
+
+ Nýrri leitarvél bætt við.
+
+ Settu inn heiti leitarvélar
+ Uppsett leitarvél er nú þegar að nota það nafn.
+
+ Settu inn leitarstreng
+
+ Athugaðu hvort leitarstrengurinn sé í sama sniði og dæmið
+
+
+ Hreinsa inntak
+
+
+ Afgreiða
+
+
+ Eyða vafurferli
+
+
+ Opnir flipar: %1$s
+
+
+ Örugg tenging
+
+
+ Hleð inn
+
+
+ Vefsvæði hlaðið
+
+
+ Fleiri valkostir
+
+
+ Hnappur fyrir fleiri valkosti
+
+
+ Fara áfram
+
+
+ Endurhlaða vefsvæði
+
+
+ Fara til baka
+
+
+ Hætta að hlaða inn vefsvæði
+
+
+ Fara aftur í fyrra forrit
+
+
+ Fjöldi rekjara sem lokað er á
+
+
+ Loka á rekjara
+
+ Réttindi þín
+
+ Opna tengil með öðru smáforriti
+
+
+ Þú getur farið af %1$s til að opna þennan tengil í %2$s.
+
+ Finndu forrit sem getur opnað tengil
+
+
+ Ekkert af forritunum í tækinu þínu getur opnað þennan tengil. Þú getur farið úr %1$s til að leita í %2$s að forriti sem getur það.
+
+ Hætta í huliðsvafri?
+
+
+ %1$s lokið
+
+
+ Opna
+
+
+ Bætt við flýtileiðir!
+
+ Netþjónn fannst ekki
+
+
+ Loka
+
+
+
+ Velkomin í %1$s
+
+
+ Hraðvirkt. Einkamál. Engar truflanir.
+
+
+ Hefjast handa
+
+
+
+ %1$s er ekki eins og aðrir vafrar
+
+
+ Við hreinsum vafurferilinn þegar þú lokar forritinu til að auka persónuvernd.
+
+
+
+ Gerðu %1$s sjálfgefið til að vernda gögnin þín við hvern tengil sem þú opnar.
+
+
+ Setja sem sjálfgefinn vafra
+
+
+ Sleppa
+
+
+ Auktu friðhelgi þína
+
+ Taktu huliðsvafur upp á næsta stig. Lokaðu fyrir auglýsingar og annað efni sem getur fylgst með þér á milli vefsvæða og eykur hleðslutíma á vefsíðum.
+
+
+ Leitin þín, á þinn hátt
+
+
+ Ertu að leita að einhverju öðru? Veldu aðra sjálfgefna leitarvél í stillingunum.
+
+
+ Bættu flýtileiðum við upphafsskjáinn þinn
+
+
+ Farðu fljótt aftur á uppáhaldsvefsvæðin þín í %1$s. Veldu bara \"Bæta við upphafsskjá\" í %1$s valmyndinni.
+
+
+ Gerðu gagnaleynd að vana
+
+ Stilltu %1$s sem sjálfgefinn vafra og fáðu ávinninginn af huliðsvafri þegar þú opnar vefsíður úr öðrum forritum.
+
+ Í lagi, ég skil!
+ Sleppa
+ Næsta
+
+
+ -
+
+
+ Bæta við
+
+ JÁ
+
+
+ Hætta við
+
+ NEI
+
+
+ Flýtileið mun opnast þegar aukin rakningarvernd er óvirk
+
+
+ Huliðsvafurlota
+
+
+ Tilkynningar gera þér kleift að eyða %1$s-lotunni með einni snertingu. Þú þarft ekki að opna forritið eða sjá hvað er í gangi í vafranum þínum.
+
+
+ Eyða vafurferli
+
+
+ Sækja Firefox
+
+
+
+
+
+ Mozilla Public License og annarra notkunarleyfa opins hugbúnaðar.]]>
+
+
+ hér.]]>
+
+
+ hugbúnaðarleyfum.]]>
+
+
+ almenna GNU General Public notkunarleyfinu útg.3 og er tiltækt hér .]]>
+
+
+ Notandanafn
+ Lykilorð
+ Hreinsa
+
+
+
+ Örugg tenging
+ Óörugg tenging
+
+ Sannvottað af: %1$s
+
+
+ Öryggi vefsvæðis
+ Slóð þegar til
+
+
+ Finna á síðu
+
+
+ Finna á síðu
+
+
+ %1$d/%2$d
+
+ %1$d af %2$d
+
+
+ Finna næstu niðurstöðu
+
+ Finna fyrri niðurstöðu
+
+ Sleppa leit á síðu
+
+
+ Fá borðtölvuvefsvæði
+
+
+ Vefsvæði fyrir borðtölvur
+
+
+ Vefslóð afrituð
+
+
+ Forritunarverkfæri
+
+
+ Opna tengla í smáforritum
+
+
+ Ítarlegt
+
+
+ Heimildir vefsvæðis
+
+
+ Fækkun vefkökuborða
+
+
+ Virkt
+
+
+ Óvirkt
+
+
+ Fækkun vefkökuborða
+
+
+ Sjáðu færri borða með því að hafna sjálfkrafa beiðnum um vefkökur, þegar mögulegt er.
+
+ -->
+ Fækkun vefkökuborða
+
+
+ Kveikt fyrir þetta vefsvæði
+
+
+ Vefsvæðið er ekki stutt í augnablikinu
+
+
+ Slökkt fyrir þetta vefsvæði
+
+
+ Fækkun vefkökuborða
+
+
+ Slökkt fyrir þetta vefsvæði
+
+
+ Kveikt fyrir þetta vefsvæði
+
+
+ Viltu kveikja á fækkun vefkökuborða fyrir %1$s?
+
+
+ Viltu slökkva á fækkun vefkökuborða fyrir %1$s?
+
+
+ %1$s mun hreinsa vefkökur þessa vefsvæðis og endurlesa síðuna. Að hreinsa allar vefkökur gæti skráð þig út eða tæmt innkaupakörfur.
+
+
+ %1$s getur reynt að hafna sjálfkrafa beiðnum um vefkökur.
+
+
+ Þetta vefsvæði styður ekki stendur fækkun vefkökuborða. Viltu biðja um að teymið okkar skoði þetta vefsvæði og bæti við stuðningi í framtíðinni?
+
+
+ Hætta við
+
+
+ Biðja um stuðning
+
+
+ Beiðni um stuðning við vefsvæði lögð fram.
+
+
+ Beiðni um stuðning við vefsvæði lögð fram.
+
+
+
+ %1$s reynir að hafna beiðnum um vefkökur til að hunsa pirrandi vefkökuborða.\n\nSýslaðu með stillingar fyrir vefkökuborða í %2$s.
+
+
+ stillingunum
+
+
+ Sjálfvirk spilun
+
+
+ Til að leyfa það:
+
+
+ 1. Farðu í Android-stillingar
+
+
+ Heimildir]]>
+
+
+ Farðu í Stillingar
+
+
+ %1$s yfir í VIRKT]]>
+
+
+ Myndavél
+
+
+ Hljóðnemi
+
+
+ Staðsetning
+
+
+ Tilkynningar
+
+
+ DRM-stýrt efni
+
+
+ Biðja um að leyfa
+
+
+ Lokað á
+
+
+ Leyft
+
+
+ Lokað af Android
+
+
+ Leyfa hljóð og myndskeið
+
+
+ Loka eingöngu fyrir hljóð
+
+
+ Mælt með
+
+
+ Loka á hljóð og myndskeið
+
+
+ Rannsóknir
+
+
+ Firefox kann að setja upp og keyra rannsóknir af og til.
+
+
+ Fræðast meira
+
+
+ Forritið hættir til að virkja breytingar
+
+
+ Fjarlægja
+
+
+ Virkt
+
+
+ Lokið
+
+
+ Kembt með fjartengingu í gegnum USB/Wi-Fi
+
+
+ Aflæsa
+
+
+ Staðfestu með því að nota fingrafar
+
+
+ Þú getur notað fingrafarið þitt til að halda áfram núverandi forritslotu.
+
+
+ Opna tengil í nýrri lotu
+
+
+ Fingrafarstákn
+
+
+ Fingrafar ekki þekkt. Reyndu aftur.
+
+
+ Fingurinn hreyfðist of hratt. Reyndu aftur.
+
+
+ Sýna leitartillögur?
+
+
+ Til að fá tillögur, þarf %1$s að senda það sem þú skrifar í slóðina til leitarvélarinnar.
+
+
+ Nei
+
+
+ Já
+
+
+ Sumar leitarvélar geta ekki sýnt tillögur.
+
+
+ Afgreiða
+
+
+
+
+ Vefsvæði hagar sér óvænt?\n
+ Prófaðu að slökkva á rakningarvörn
+
+
+ Bæta við upphafsskjá]]>
+
+
+ Opnaðu alla tengla í %1$s\n
+ Stilltu %1$s sem sjálfgefinn vafra
+
+
+
+ Sjálfvirk útfylling vefslóða fyrir vefsvæði sem þú notar mest\n
+ Ýttu lengi á hvaða vefslóð sem er í veffangastikunni
+
+
+
+ Opnaðu tengil í nýjum flipa\n
+ Ýttu lengi á hvaða tengil sem er á síðu
+
+
+
+ Slökkva á ábendingum á upphafsskjánum
+
+
+ Nýr flipi opnaður
+
+
+ Skipta
+
+
+ Fara í fullan skjá
+
+
+ Skipta strax yfir í tengil í nýjum flipa
+
+
+ Loka á hugsanlega hættuleg og villandi vefsvæði
+
+ Lokaðu fyrir tilkynnt vefsvæði með villandi efni og árásum, spilliforrit og vefsvæði með óæskilegum hugbúnaði.
+
+ Einungis-HTTPS-hamur
+
+ Reynir sjálfkrafa að tengjast vefsvæðum með HTTPS-dulritunareglum til að auka öryggi.
+
+
+ Undantekningar
+
+ Þú hefur gert útilokun efnis óvirka á þessum vefvæðum.
+
+ Fjarlægja
+
+ Fjarlægja öll vefsvæði
+
+
+ Loka á vefkökur
+
+
+ Viltu loka á vefkökur?
+
+
+ Flipi hrundi
+
+ Því miður. Við erum í vandræðum með þennan flipa.
+
+ Þar sem þetta er huliðsvafri, vistum við aldrei og getum ekki endurheimt þennan flipa.
+
+ Loka flipa
+
+
+ Senda hrunskýrslu til Mozilla
+
+
+
+
+ Rekjarar sem lokað er fyrir síðan %s
+
+ Efni
+
+ Auglýsingar
+
+ Samfélagsmiðlar
+
+ Greiningar
+
+ Aukin rakningarvörn
+
+ SLÖKKT er á vörn fyrir þetta vefssvæði
+
+ KVEIKT er á vörn fyrir þetta vefsvæði
+
+ Tenging er örugg
+
+ Tenging er ekki örugg
+
+ Rekjarar og skriftur sem á að útiloka
+
+
+ Til baka
+
+
+
+ Fjarlægja
+
+ Endurnefna
+
+ Endurnefna
+
+ Nafn flýtileiðar
+
+
+ Vistuðum og deildum myndum <b>verður ekki</b> eytt þegar þú eyðir ferli %1$s
+
+
+
+ Þema
+
+ Ljóst
+
+ Dökkt
+
+
+ Stillt af rafhlöðusparnaðarkerfi
+
+ Fylgja þema tækisins
+
+
+ Þetta vefsvæði styður ekki HTTPS
+
+
+ Frekari upplýsingar
+ Breyttu þessari stillingu í Stillingar > Persónuvernd og öryggi > Öryggi.]]>
+
+
+ Tenging er ekki örugg
+
+
+
+Ef þú hefur getað tengst þessum netþjóni án vandamála áður, gæti verið að vandamálið sé aðeins tímabundið, þannig að þú gætir reynt aftur seinna.
+ ]]>
+
+
+ Einhver gæti verið að reyna að þykjast vera þetta vefsvæði og þú ættir ekki að halda áfram.
+
+ %1$s treystir ekki %2$s vegna þess að útgefandi skilríkisins er óþekktur, skilríkið er gefið út af sjálfu sér eða þjóninn er ekki að senda rétt milliskilríki. ]]>
+
+
+
+ Loka flipa
+
+
+
+ Náðonum! Við komum í veg fyrir að þetta vefsvæði njósnaði um þig. Ýttu á skjöldinn til að fá upplýsingar um það sem við lokuðum á.
+
+
+ Loka sprettglugga
+
+
+
+ Þú ert varin/n!
+
+ Þessar sjálfgefnu stillingar bjóða upp á öfluga vernd. En það er auðvelt að laga til stillingarnar til að mæta sérþörfum þínum.
+
+ Afgreiða
+
+
+ Ýttu hér til að henda öllu - ferli, vefkökum, öllu - og byrjaðu upp á nýtt á nýjum flipa.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Loka
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Viðmótshluti fyrir leit
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Vafurferill hreinsaður 🎉
+
+
+ Byrjaðu huliðsvafralotuna þína og við lokum á rakningar og annað vont dót á meðan þú vafrar.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Við látum þig vera í huliðsvafrinu þínu, en byrjum hraðar næst með %1$s-viðmótshlutanum á upphafsskjánum þínum.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Bæta viðmótshluta á upphafsskjá
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Viðmótshluta bætt á upphafsskjá
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-it/strings.xml b/mobile/android/focus-android/app/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000000..cf0e6b6abd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,1122 @@
+
+
+
+
+
+
+
+
+ Annulla
+
+ OK
+
+ Salva
+
+
+ Cerca o inserisci un indirizzo
+
+ Navigazione anonima in automatico.\nNaviga. Elimina. Ripeti.
+
+
+ La cronologia di navigazione è stata eliminata.
+
+ Cronologia di navigazione eliminata
+
+
+ Eliminata cronologia di navigazione della scheda.
+
+
+ Cerca %1$s
+
+
+ Condividi…
+
+
+ Segnala un problema con questo sito
+
+
+ Apri in %1$s
+
+
+ Apri in…
+
+
+ Agg. a schermata principale
+
+
+ Aggiungi alle scorciatoie
+
+ Rimuovi dalle scorciatoie
+
+
+ Impostazioni
+ Informazioni su
+ Aiuto
+ I tuoi diritti
+
+
+ Traccianti bloccati
+
+
+ Disattivare questa opzione potrebbe risolvere alcuni problemi con i siti
+
+
+ Blocco contenuti
+
+ Disattivare questa opzione può risolvere problemi con alcuni siti
+
+
+ Con tecnologia %1$s
+
+
+ Condividi con
+
+ Eliminare la cronologia di navigazione?
+ Tocca o cancella questa notifica per eliminare in modo sicuro la cronologia di navigazione.
+
+
+ Tocca o fai scorrere questa notifica per eliminare in modo sicuro la cronologia di navigazione.
+
+ Elimina cronologia di navigazione
+
+
+ Apri
+
+
+ Elimina dati e apri
+
+
+ Elimina
+
+
+ Elimina cronologia di navigazione
+
+
+
+ Elimina e apri
+
+
+ Elimina e apri %1$s
+
+
+
+ Cerca in Focus
+
+ Cerca in Klar
+
+ Cerca in Focus Beta
+
+ Cerca in Focus Nightly
+
+
+ Prendi il controllo con %1$s.
+Utilizzalo per la navigazione anonima:
+
+ Cerca e naviga direttamente nell’app.
+ Blocca il tracciamento (o aggiorna le impostazioni per consentirlo).
+ Utilizza “Elimina” per rimuovere i cookie così come la cronologia di ricerca e navigazione.
+
+
+%1$s è realizzato da Mozilla. La nostra missione è promuovere un Internet integro e aperto.
+Ulteriori informazioni
]]>
+
+
+ Privacy e sicurezza
+
+
+ Opzioni per gestire tracciamento, cookie e dati
+
+
+ Imposta predefinito, completamento automatico
+
+
+
+
+ Informazioni su %1$s, supporto
+
+
+ Protezione antitracciamento avanzata
+
+
+ Contenuti web
+
+
+ Cambio di app
+
+
+ Generale
+
+
+ Browser predefinito, lingua
+
+
+ Raccolta e uso dati
+
+ Ricerca
+
+
+ Ottieni suggerimenti di ricerca
+
+ Ciò che digiti nella barra degli indirizzi verrà inviato da %1$s al motore di ricerca
+
+
+ Predefinito
+
+
+ Motore di ricerca
+
+
+ Attivo
+
+
+ Disattivato
+
+
+ Completamento automatico indirizzo
+
+
+ Per siti principali
+
+
+ Attiva per completare automaticamente oltre 450 domini nella barra degli indirizzi di %s.
+
+
+ Per siti aggiunti da te
+
+
+ Attiva per fare in modo che %s completi automaticamente i tuoi indirizzi preferiti.
+
+
+ Gestione siti
+
+
+ Gestisci siti
+
+
+ + Aggiungi indirizzo personalizzato
+
+
+ Il tuo elenco di completamento automatico:
+
+
+ Aggiungi URL
+
+
+ Nuovo indirizzo pers.
+
+
+ Nuovo indirizzo pers.
+
+
+ Aggiungi link per completamento automatico
+
+
+ Cookie e dati siti web
+
+
+ Opzioni gestione dati
+
+
+ Rimozione indirizzi pers.
+
+
+ Ulteriori informazioni
+
+
+ Aggiungi e gestisci il completamento automatico degli indirizzi personalizzati.
+
+
+ Indirizzo da aggiungere
+
+
+ Incolla o inserisci un indirizzo
+
+
+ Esempio: mozilla.org
+
+
+ Esempio: example.com
+
+
+ Aggiunto nuovo indirizzo personalizzato.
+
+
+ Elimina
+
+
+ Elimina
+
+
+ Verifica l’indirizzo inserito.
+
+ Lingua
+
+ Predefinita del sistema
+
+ Privacy
+ Blocca pubblicità traccianti
+ Alcuni annunci pubblicitari, senza bisogno di aprirli, sono in grado di tracciare i siti visitati
+ Blocca traccianti analitici
+ Utilizzati per raccogliere, analizzare e misurare attività come tocchi e scorrimenti
+ Blocca traccianti social
+ Inseriti nei siti per tenere traccia delle visite e fornire funzionalità come i pulsanti di condivisione
+ Blocca altri contenuti traccianti
+ L’attivazione potrebbe causare problemi di funzionamento in alcune pagine
+ Blocca cookie
+
+
+ No, grazie
+ Blocca solo cookie traccianti di terze parti
+ Blocca solo cookie di terze parti
+
+ Blocca cookie intersito
+ Sì, grazie
+
+
+ Usa l’impronta digitale per sbloccare l’app
+
+
+ Sblocca utilizzando l’impronta digitale se hai aggiunto scorciatoie o quando un sito web è già aperto in %s.
+
+
+ Invisibile
+
+ Nascondi le pagine web durante il passaggio tra le app e impedisci l’acquisizione di schermate.
+
+ Sicurezza
+
+ Prestazioni
+ Blocca web font
+
+ Alcune immagini e icone potrebbero non essere visualizzate
+
+ Blocca JavaScript
+
+ Il caricamento delle pagine può velocizzarsi, ma potrebbero anche verificarsi errori imprevisti
+
+
+ Imposta %1$s come browser predefinito
+
+ Mozilla
+ Invia dati sull’uso
+
+
+ Ulteriori informazioni
+
+
+ Mozilla cerca di raccogliere solo i dati necessari per fornire e migliorare %1$s per tutti gli utenti.
+
+
+ Informativa sulla privacy
+
+
+ Informazioni sulla licenza
+
+
+ Librerie utilizzate
+
+
+ %s | Librerie OSS
+
+
+ Informazioni su %1$s
+
+
+ Motori di ricerca installati
+
+
+ Seleziona motore di ricerca
+
+
+ Ripristina motori di ricerca predefiniti
+
+
+ + Aggiungi un altro motore di ricerca
+ Rimozione motori di ricerca
+ Rimuovi
+
+
+ Aggiungi un altro motore di ricerca
+
+ Seleziona il motore di ricerca preferito:
+
+
+ Aggiungi motore di ricerca
+
+ Nome del motore di ricerca
+ Stringa di ricerca da utilizzare
+ Salva
+
+
+ Esempio: example.com/search/?q=%s
+
+ Aggiunto nuovo motore di ricerca.
+
+ Inserire il nome del motore di ricerca
+ È già presente un motore di ricerca con questo nome.
+
+ Inserire la stringa di ricerca
+
+ Verifica che la stringa di ricerca corrisponda al formato di esempio
+
+
+ Cancella testo
+
+
+ Chiudi
+
+
+ Elimina cronologia di navigazione
+
+
+ Schede aperte: %1$s
+
+
+ Connessione sicura
+
+
+ Caricamento…
+
+
+ Sito web caricato
+
+
+ Altre opzioni
+
+
+ Pulsante altre opzioni
+
+
+ Naviga in avanti
+
+
+ Ricarica il sito web
+
+
+ Torna indietro
+
+
+ Interrompi il caricamento del sito
+
+
+ Torna all’applicazione precedente
+
+
+ Numero di tracciamenti bloccati
+
+
+ Blocca elementi traccianti
+
+ I tuoi diritti
+
+ Apri link in un’altra app
+
+ È possibile uscire da %1$s e aprire questo link in %2$s.
+
+ Trova un app per aprire il link
+
+ Nessuna delle app sul dispositivo è in grado di aprire questo link. È possibile uscire da %1$s e cercare una app adatta in %2$s.
+
+ Uscire da Navigazione anonima?
+
+
+ %1$s completato
+
+
+ Apri
+
+
+
+
+
+
+
+
+
+
+ Aggiunto alle scorciatoie.
+
+ Impossibile contattare il server
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Chiudi
+
+
+
+ Benvenuto in %1$s
+
+
+ Veloce. Privato. Nessuna distrazione.
+
+
+ Inizia
+
+
+
+ %1$s non è come gli altri browser
+
+
+ Cancelliamo la tua cronologia quando chiudi l’app per garantirti una maggiore privacy.
+
+
+
+ Imposta %1$s come browser predefinito per proteggere i tuoi dati quando apri qualsiasi link.
+
+
+ Imposta come browser predefinito
+
+
+ Ignora
+
+
+
+ Potenzia la tua privacy
+
+ Porta la navigazione anonima a un livello superiore. Blocca pubblicità e altri contenuti che cercano di seguirti attraverso siti diversi e rallentano il caricamento delle pagine.
+
+
+ La ricerca a modo tuo
+
+ Stavi cercando qualcos’altro? Scegli un altro motore di ricerca predefinito nelle Impostazioni.
+
+
+ Aggiungi collegamenti alla schermata principale
+
+ Ritorna più velocemente ai tuoi siti preferiti con %1$s. Seleziona “Agg. a schermata principale” dal menu di %1$s.
+
+
+ Trasforma la privacy in un’abitudine
+
+ Imposta %1$s come browser predefinito e goditi i vantaggi della navigazione anonima quando apri pagine web da altre app.
+
+ OK
+ Ignora
+ Avanti
+
+
+ -
+
+
+ Aggiungi
+
+
+ SÌ
+
+
+ Annulla
+
+
+ NO
+
+
+ Questa scorciatoia verrà aperta con la Protezione antitracciamento avanzata disattivata
+
+
+ Sessione di navigazione anonima
+
+
+ Le notifiche permettono di eliminare i dati della sessione di %1$s con un tocco. Non serve aprire l’app o controllare il contenuto del browser.
+
+
+ Elimina cronologia di navigazione
+
+
+ Scarica Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License e di altre licenze Open Source.]]>
+
+
+ questa pagina.]]>
+
+
+ licenze libere e Open Source.]]>
+
+
+ blocklist fornito da Disconnect, Inc. in qualità di opera aggiuntiva e separata, distribuita nei termini della GNU General Public License v3 e disponibile qui .]]>
+
+
+ Nome utente
+ Password
+ Cancella
+
+
+
+ Connessione sicura
+ Connessione non sicura
+
+ Verificata da: %1$s
+
+
+ Sicurezza del sito
+ Indirizzo già presente
+
+
+ Trova nella pagina
+
+
+ Trova nella pagina
+
+
+ %1$d/%2$d
+
+ %1$d di %2$d
+
+
+ Risultato successivo
+
+ Risultato precedente
+
+ Abbandona ricerca
+
+
+
+
+ Richiedi sito desktop
+
+
+ Sito desktop
+
+
+ URL copiato
+
+
+ Strumenti di sviluppo
+
+
+ Apri i link nelle app
+
+
+ Avanzate
+
+
+ Permessi dei siti
+
+
+ Riduzione banner per i cookie
+
+
+ Attiva
+
+
+ Disattivata
+
+
+ Riduzione banner per i cookie
+
+
+ Visualizza meno banner rifiutando automaticamente, dove possibile, le richieste di cookie.
+
+ -->
+ Riduzione banner per i cookie
+
+
+ ATTIVA per questo sito
+
+
+ Sito attualmente non supportato
+
+
+ DISATTIVATA per questo sito
+
+
+ Riduzione banner per i cookie
+
+
+ DISATTIVATA per questo sito
+
+
+ ATTIVA per questo sito
+
+
+ Attivare la riduzione banner per i cookie su %1$s?
+
+
+ Disattivare la riduzione banner per i cookie su %1$s?
+
+
+ %1$s eliminerà i cookie per questo sito e aggiornerà la pagina. L’eliminazione dei cookie potrebbe disconnetterti dal sito o svuotare eventuali carrelli in sospeso.
+
+
+ %1$s può cercare di rifiutare automaticamente le richieste di cookie.
+
+
+ Questo sito non è attualmente supportato dalla funzione per ridurre i banner per i cookie. Vuoi chiedere al nostro team di valutare questo sito web e aggiungere supporto in futuro?
+
+
+ Annulla
+
+
+ Richiedi supporto
+
+
+ Richiesta di supportare il sito inviata.
+
+
+ Richiesta di supportare il sito inviata.
+
+
+
+ %1$s cerca di rifiutare le richieste di cookie per chiudere i fastidiosi banner per i cookie.\n\nGestisci le preferenze relative ai banner per i cookie nelle %2$s.
+
+
+ impostazioni
+
+
+ Riproduzione automatica
+
+
+ Per consentirlo:
+
+
+ 1. Apri le impostazioni di Android
+
+
+ Autorizzazioni]]>
+
+
+ Apri Impostazioni
+
+
+ %1$s su Consenti]]>
+
+
+ Fotocamera
+
+
+ Microfono
+
+
+ Posizione
+
+
+ Notifiche
+
+
+ Contenuti protetti da DRM
+
+
+ Chiedi il consenso
+
+
+ Bloccato
+
+
+ Consentito
+
+
+ Bloccato da Android
+
+
+ Consenti audio e video
+
+
+ Blocca solo l’audio
+
+
+ Consigliato
+
+
+ Blocca audio e video
+
+
+ Studi
+
+
+ Firefox può installare e condurre degli studi di tanto in tanto.
+
+
+ Ulteriori informazioni
+
+
+ Per applicare le modifiche l’applicazione verrà chiusa
+
+
+ Rimuovi
+
+
+ Attivi
+
+
+ Completati
+
+
+ Debug remoto attraverso USB/Wi-Fi
+
+
+ Sblocca
+
+
+ Conferma utilizzando la tua impronta digitale
+
+
+ Puoi utilizzare la tua impronta digitale per continuare la sessione corrente dell’app.
+
+
+ Apri link in nuova sessione
+
+
+ Icona dell’impronta digitale
+
+
+ Impronta digitale non riconosciuta. Riprova.
+
+
+ Dito rimosso troppo presto. Riprova.
+
+
+ Mostrare i suggerimenti di ricerca?
+
+
+ Per ricevere suggerimenti, %1$s deve inviare ciò che digiti nella barra degli indirizzi al motore di ricerca.
+
+
+ No
+
+
+ Sì
+
+
+ I suggerimenti non sono disponibili per tutti i motori di ricerca.
+
+
+ Ignora
+
+
+
+
+ Un sito mostra un comportamento anomalo?\n Prova a disattivare la protezione antitracciamento
+
+
+ Agg. a schermata principale]]>
+
+
+ Apri ogni link in %1$s\n Imposta %1$s come browser predefinito
+
+
+ Completamento automatico degli indirizzi per i siti che visiti più spesso\n Tieni premuto più a lungo un indirizzo URL nella barra degli indirizzi
+
+
+ Apri un link in una nuova scheda\n Tieni premuto a lungo un link in una pagina
+
+
+ Disattiva suggerimenti nella schermata iniziale
+
+
+ Aperta nuova scheda
+
+
+ Passa a
+
+
+ Passato a schermo intero
+
+
+ Passa direttamente al link nella nuova scheda
+
+
+ Blocca siti potenzialmente pericolosi e ingannevoli
+
+ Blocca i siti che sono stati segnalati come malevoli o ingannevoli, contenenti malware o software indesiderato.
+
+
+ Modalità solo HTTPS
+
+
+ Tenta automaticamente la connessione ai siti utilizzando il protocollo di crittografia HTTPS per una maggiore sicurezza.
+
+
+ Eccezioni
+
+ Il blocco contenuti è disattivato per i seguenti siti.
+
+ Rimuovi
+
+ Rimuovi tutti i siti
+
+
+ Blocca cookie
+
+
+ Bloccare i cookie?
+
+
+ Scheda bloccata
+
+ Si è verificato un problema con questa scheda.
+
+ Per motivi di riservatezza le tue schede non vengono salvate e, di conseguenza, non possono essere ripristinate.
+
+ Chiudi scheda
+
+
+
+
+
+ Invia la segnalazione di arresto anomalo a Mozilla
+
+
+
+
+ Elementi traccianti bloccati dal %s
+
+ Contenuti
+
+ Annunci pubblicitari
+
+ Social
+
+ Statistiche
+
+ Protezione antitracciamento avanzata
+
+ Le protezioni sono DISATTIVATE per questo sito
+
+ Le protezioni sono ATTIVE per questo sito
+
+ La connessione è sicura
+
+ La connessione non è sicura
+
+ Elementi traccianti e script da bloccare
+
+
+ Torna indietro
+
+
+
+ Rimuovi
+
+
+ Rinomina
+
+ Rinomina
+
+
+ Nome scorciatoia
+
+
+ Le immagini salvate e condivise <b>non verranno eliminate</b> quando si elimina la cronologia di %1$s.
+
+
+
+ Tema
+
+ Chiaro
+
+ Scuro
+
+ Impostato da Risparmio energetico
+
+ Usa il tema del dispositivo
+
+
+
+ Questo sito non supporta HTTPS
+
+
+ Ulteriori informazioni
+ Modifica questa impostazione in Impostazioni > Privacy e sicurezza > Sicurezza.]]>
+
+
+ Connessione non sicura
+
+
+
+ Se è stato possibile connettersi a questo server in passato, il problema potrebbe essere solo temporaneo.
+ ]]>
+
+
+ Potrebbe trattarsi di un tentativo di sostituirsi al sito originale e continuare potrebbe essere rischioso.
+
+ %1$s non considera attendibile %2$s in quanto l’emittente del certificato è sconosciuto, il certificato è autofirmato oppure il server non ha inviato i certificati intermedi previsti.
+ ]]>
+
+
+
+ Chiudi scheda
+
+
+
+ Preso! Abbiamo impedito a questo sito di spiarti. Tocca lo scudo in qualsiasi momento per vedere che cosa viene bloccato.
+
+
+ Chiudi pop-up
+
+
+
+ Sei protetto!
+
+
+ Queste impostazioni predefinite offrono una buona protezione. In ogni caso è facile modificare le impostazioni per soddisfare le tue esigenze specifiche.
+
+ Chiudi
+
+
+ Tocca qui per eliminare tutti i dati — cronologia, cookie, tutto! — e ricominciare da capo con una nuova scheda.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Chiudi
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget di ricerca
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Cronologia di navigazione eliminata 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Inizia la tua sessione di navigazione anonima. Nel frattempo, noi rimuoveremo elementi traccianti e altre minacce.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Ti lasciamo alla tua navigazione privata, ma la prossima volta ricorda che puoi iniziare più rapidamente utilizzando il widget di %1$s nella schermata principale.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Aggiungi widget alla schermata principale
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget aggiunto alla schermata principale
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-iw/strings.xml b/mobile/android/focus-android/app/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000000..341da731a3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-iw/strings.xml
@@ -0,0 +1,1093 @@
+
+
+
+
+
+
+
+
+ ביטול
+
+ אישור
+
+ שמירה
+
+
+ חיפוש או הכנסת כתובת
+
+ גלישה פרטית אוטומטית.\nגלישה. מחיקה. חזרה להתחלה.
+
+
+ היסטוריית הגלישה שלך נמחקה.
+ היסטוריית הגלישה נמחקה
+
+
+ היסטוריית הגלישה של הלשונית נמחקה.
+
+
+ חיפוש עבור %1$s
+
+
+ שיתוף…
+
+
+ דיווח על בעיה באתר
+
+
+ פתיחה ב־%1$s
+
+
+ פתיחה ב…
+
+
+ הוספה למסך הבית
+
+
+ הוספה לקיצורי הדרך
+
+ הסרה מקיצורי הדרך
+
+
+ הגדרות
+ על אודות
+ עזרה
+ הזכויות שלך
+
+
+ רכיבי מעקב חסומים
+
+
+ כיבוי אפשרות זו עשוי לתקן בעיות מסוימות באתרים
+
+
+ חסימת תוכן
+
+ כיבוי תיקוני אתרים
+
+
+ מופעל על־ידי %1$s
+
+
+ שיתוף באמצעות
+
+ למחוק את היסטוריית הגלישה?
+
+ יש להקיש או לנקות התרעה זו כדי למחוק בצורה מאובטחת את היסטוריית הגלישה שלך.
+
+
+ יש להקיש או להחליק על התרעה זו כדי למחוק בצורה מאובטחת את היסטוריית הגלישה שלך.
+
+ מחיקת היסטורית גלישה
+
+
+ פתיחה
+
+
+ מחיקה ופתיחה
+
+
+ מחיקה
+
+
+ מחיקת היסטורית גלישה
+
+
+
+ מחיקה ופתיחה
+
+
+ מחיקה ופתיחה של %1$s
+
+
+
+ חיפוש ב־Focus
+
+ חיפוש ב־Klar
+
+ חיפוש ב־Focus Beta
+
+ חיפוש ב־Focus Nightly
+
+
+ פרטיות ואבטחה
+
+
+ מעקב, עוגיות, בחירות נתונים
+
+
+ הגדרת בררת מחדל, השלמה אוטומטית
+
+
+
+
+ על אודות %1$s, עזרה
+
+
+ הגנת מעקב מתקדמת
+
+
+ תוכן מקוון
+
+
+ מעבר בין יישומים
+
+
+ כללי
+
+
+ דפדפן ברירת מחדל, שפה
+
+
+ איסוף ושימוש במידע
+
+ חיפוש
+
+
+ קבלת הצעות חיפוש
+
+ %1$s ישלח מה שיוקלד בשורת הכתובת אל מנוע החיפוש שלך
+
+
+ ברירת מחדל
+
+
+ מנוע חיפוש
+
+
+ פעיל
+
+
+ כבוי
+
+
+ השלמת כתובת אוטומטית
+
+
+ לאתרים המובילים
+
+
+ יש להפעיל כדי ש־%s ישלים אוטומטית למעלה מ־450 כתובות נפוצות בשורת הכתובת.
+
+
+ לאתרים שהוספת
+
+
+ יש להפעיל כדי ש־%s ישלים אוטומטית את כתובות האתרים המועדפות עליך.
+
+
+ ניהול אתרים
+
+
+ ניהול אתרים
+
+
+ + הוספת כתובת מותאמת אישית
+
+
+ רשימת ההשלמה האוטומטית שלך:
+
+
+ הוספת כתובת
+
+
+ הוספת כתובת מותאמת אישית
+
+
+ הוספת כתובת בהתאמה אישית
+
+
+ הוספת קישור להשלמה אוטומטית
+
+
+ עוגיות ונתוני אתרים
+
+
+ בחירות נתונים
+
+
+ הסרת כתובות מותאמות אישית
+
+
+ מידע נוסף
+
+
+ הוספה וניהול של כתובת השלמה אוטומטית בהתאמה אישית.
+
+
+ כתובת להוספה
+
+
+ נא להדביק או להקליד כתובת
+
+
+ לדוגמה: mozilla.org
+
+
+ דוגמה: example.com
+
+
+ נוספה כתובת מותאמת אישית חדשה.
+
+
+ הסרה
+
+
+ הסרה
+
+
+ נא לבדוק שוב את הכתובת שהקלדת.
+
+ שפה
+
+ ברירת מחדל של המערכת
+
+ פרטיות
+ חסימת רכיבי מעקב
+ חלק מהפרסומות עוקבות אחר הביקורים באתרים, אפילו אם לא לחצת על הפרסומות
+ חסימת מעקב של כלי ניתוח
+ משמשות לאיסוף ניתוח ומדידת פעילויות כגון לחיצה וגלילה
+ חסימת רכיבי מעקב חברתיים
+ מוטמעות באתרים לצורך מעקב אחר הביקורים שלך והצגת רכיבים כגון כפתורי שיתוף
+ חסימת כלי מעקב אחרים
+ הפעלת התכונה עשויה לגרום לחלק מהאתרים להתנהג שלא כצפוי
+ חסימת עוגיות
+
+
+ לא תודה
+ חסימת עוגיות מעקב צד־שלישי בלבד
+ חסימת עוגיות צד־שלישי בלבד
+
+ חסימת עוגיות חוצות אתרים
+ כן בבקשה
+
+
+ שימוש בטביעת אצבע כדי לשחרר את נעילת היישומון
+
+
+ ביטול הנעילה באמצעות טביעת אצבע אם הוספת קיצורי דרך או כאשר אתר אינטרנט כבר פתוח ב%s.
+
+
+ חשאיות
+
+ הסתרת דפי אינטרנט בעת מעבר בין יישומונים וחסימת האפשרות לצילום המסך.
+
+ אבטחה
+
+ ביצועים
+ חסימת גופני רשת
+
+ עשוי לגרום לסמלים או תמונות חסרים
+
+ חסימת JavaScript
+
+ הדפים עשויים להיטען מהר יותר, אך עשויים גם להתנהג באופן בלתי צפוי
+
+
+ הגדרת %1$s כדפדפן ברירת המחדל
+
+ Mozilla
+ שליחת נתוני שימוש
+
+
+ מידע נוסף
+
+
+ Mozilla חותרת לאסוף רק מה שנדרש לצורך שיפור %1$s לטובת הכלל.
+
+
+ הצהרת פרטיות
+
+
+ מידע רישוי
+
+
+ ספריות בהן אנו משתמשים
+
+
+ %s | ספריות OSS
+
+
+ על אודות %1$s
+
+
+ מנועי חיפוש מותקנים
+
+
+ בחירת מנועי חיפוש
+
+
+ שחזור מנועי חיפוש ברירת מחדל
+
+
+ + הוספת מנוע חיפוש נוסף
+ הסרת מנועי חיפוש
+ הסרה
+
+
+ הוספת מנוע חיפוש נוסף
+
+ בחירת המנוע המועדף עליך:
+
+
+ הוספת מנוע חיפוש
+
+ שם מנוע החיפוש
+ מחרוזת חיפוש לשימוש
+ שמירה
+
+
+ דוגמה: example.com/search/?q=%s
+
+ נוסף מנוע חיפוש חדש.
+
+ נא להקליד שם למנוע החיפוש
+ קיים מנוע חיפוש מותקן בעל שם זהה.
+
+ נא להקליד מחרוזת חיפוש
+
+ יש לבדוק שמחרוזת החיפוש תואמת לתבנית שבדוגמה
+
+
+ מחיקת קלט
+
+
+ התעלמות
+
+
+ מחיקת היסטורית גלישה
+
+
+ לשוניות פתוחות: %1$s
+
+
+ חיבור מאובטח
+
+
+ בטעינה
+
+
+ האתר נטען
+
+
+ אפשרויות נוספות
+
+
+ כפתור אפשרויות נוספות
+
+
+ ניווט קדימה
+
+
+ טעינה מחדש של האתר
+
+
+ ניווט אחורה
+
+
+ עצירת טעינת האתר
+
+
+ חזרה ליישום קודם
+
+
+ מספר רכיבי המעקב החסומים
+
+
+ חסימת רכיבי מעקב
+
+ הזכויות שלך
+
+ פתיחת קישור ביישומון אחר
+
+ ניתן לעזוב את %1$s כדי לפתוח את הקישור הזה ב־%2$s.
+
+ חיפוש יישומון שיוכל לפתוח את הקישור
+
+ אף אחד מהיישומים שבמכשיר שלך יכולים לפתוח את הקישור הזה. ניתן לעזוב את %1$s כדי לחפש תחת %2$s יישומון שיכול לבצע זאת.
+
+ לצאת ממצב גלישה פרטית?
+
+
+ %1$s הסתיים
+
+
+ פתיחה
+
+
+
+
+
+
+
+
+
+
+ נוסף לקיצורי הדרך!
+
+ שרת לא נמצא
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ סגירה
+
+
+
+ ברוכים הבאים אל %1$s
+
+
+ מהיר. פרטי. ללא הסחות דעת.
+
+
+ תחילת עבודה
+
+
+
+ %1$s אינו דומה לדפדפנים אחרים
+
+
+ אנו מנקים את ההיסטוריה שלך כאשר היישומון נסגר, לפרטיות מוגברת.
+
+
+
+ ניתן להפוך את %1$s לדפדפן ברירת המחדל שלך כדי להגן על הנתונים שלך עם כל קישור שנפתח.
+
+
+ הגדרה כדפדפן ברירת המחדל
+
+
+ דילוג
+
+
+
+ חיזוק הפרטיות שלך
+
+ לקחת את הגלישה המאובטחת לרמה הבאה. ניתן לחסום פרסומות ותוכן אחר שעשוי לסייע במעקב אחריך בין אתרים שונים ולהפריע לזמני טעינת עמודים.
+
+
+ החיפוש שלך, בדרך שלך
+
+ לא מה שחיפשת? ניתן לבחור במנוע חיפוש אחר כברירת מחדל בהגדרות.
+
+
+ הוספת קיצורי דרך למסך הבית שלך
+
+ ניתן לחזור לאתרים המועדפים עליך ב־%1$s בקלות. עליך פשוט לבחור באפשרות „הוספה למסך הבית” מהתפריט %1$s.
+
+
+ להפוך את הפרטיות להרגל
+
+ ניתן להגדיר את %1$s בתור דפדפן ברירת המחדל שלך ולקבל את היתרונות של גלישה פרטים בעת פתיחת עמודי אינטרנט מיישומונים אחרים.
+
+ בסדר, הבנתי!
+ דילוג
+ הבא
+
+
+ -
+
+
+ הוספה
+
+
+ כן
+
+
+ ביטול
+
+
+ לא
+
+
+ קיצור הדרך ייפתח ללא הגנת המעקב המתקדמת
+
+
+ הפעלה בגלישה פרטית
+
+
+ התרעות מאפשרות לך למחוק את ההפעלה של %1$s בנגיעה קלה. אין צורך לפתוח את היישומון או לראות מה פועל בדפדפן שלך.
+
+
+ מחיקת היסטורית גלישה
+
+
+ הורדת Firefox
+
+
+
+
+
+
+
+
+ תנאי הרשיון הציבורי של Mozilla ורשיונות קוד פתוח אחרים.]]>
+
+
+ רישיונות קוד פתוח נוספים.]]>
+
+
+ גרסה 3 של הרשיון הציבורי הכללי של GNU, והוא זמין כאן .]]>
+
+
+ שם משתמש
+ ססמה
+ ניקוי
+
+
+
+ חיבור מאובטח
+ חיבור לא מאובטח
+
+ מאומת ע״י: %1$s
+
+
+ אבטחת האתר
+ הכתובת כבר קיימת
+
+
+ חיפוש בדף
+
+
+ חיפוש בדף
+
+
+ %1$d/%2$d
+
+ %1$d מתוך %2$d
+
+
+ חיפוש התוצאה הבאה
+
+ חיפוש התוצאה הקודמת
+
+ סגירת החיפוש בדף
+
+
+
+
+ בקשת אתר למחשבים
+
+
+ אתר למחשבים
+
+
+ הכתובת הועתקה
+
+
+ כלי פיתוח
+
+
+ פתיחת קישורים ביישומונים
+
+
+ מתקדם
+
+
+ הרשאות אתר
+
+
+ צמצום כרזות עוגיות
+
+
+ פעילה
+
+
+ כבויה
+
+
+ צמצום כרזות עוגיות
+
+ -->
+ צמצום כרזות עוגיות
+
+
+ פעיל עבור אתר זה
+
+
+ האתר לא נתמך כרגע
+
+
+ כבוי עבור אתר זה
+
+
+ צמצום כרזות עוגיות
+
+
+ כבוי עבור אתר זה
+
+
+ פעיל עבור אתר זה
+
+
+ להפעיל צמצום כרזות עוגיות לאתר %1$s?
+
+
+ להשבית צמצום כרזות עוגיות לאתר %1$s?
+
+
+ %1$s ינקה את העוגיות של אתר זה וירענן את הדף. ניקוי כל העוגית עשוי לנתק את החשבון שלך מהאתר או לרוקן את עגלת הקניות שלך.
+
+
+ %1$s יכול לנסות לדחות באופן אוטומטי בקשות כרזות עוגיות.
+
+
+ ביטול
+
+
+ בקשת תמיכה
+
+
+ הבקשה לתמיכה באתר הוגשה.
+
+
+ הבקשה לתמיכה באתר הוגשה.
+
+
+ הגדרות
+
+
+ ניגון אוטומטי
+
+
+ כדי לאפשר את ההרשאה:
+
+
+ 1. יש לעבור להגדרות של Android
+
+
+ הרשאות]]>
+
+
+ מעבר להגדרות
+
+
+ %1$s לכדי פעיל]]>
+
+
+ מצלמה
+
+
+ מיקרופון
+
+
+ מיקום
+
+
+ התרעה
+
+
+ תוכן מוגן DRM
+
+
+ לבקש לאפשר
+
+
+ חסום
+
+
+ מאופשר
+
+
+ חסום על־ידי Android
+
+
+ הפעלת שמע ווידאו
+
+
+ חסימת שמע בלבד
+
+
+ מומלץ
+
+
+ חסימת שמע ווידאו
+
+
+ מחקרים
+
+
+ Firefox עשוי להתקין ולהריץ מחקרים מדי פעם בפעם.
+
+
+ מידע נוסף
+
+
+ היישומון ייסגר להחלת השינויים
+
+
+ הסרה
+
+
+ פעיל
+
+
+ הושלמו
+
+
+ ניפוי שגיאות מרחוק דרך USB/Wi-Fi
+
+
+ שחרור נעילה
+
+
+ נא לאשר באמצעות טביעת האצבע שלך
+
+
+ באפשרותך להשתמש בטביעת האצבע שלך כדי להמשיך בהפעלת היישומון הנוכחית שלך.
+
+
+ פתיחת קישור בהפעלה חדשה
+
+
+ סמל טביעת אצבע
+
+
+ טביעת האצבע לא זוהתה. נא לנסות שוב.
+
+
+ האצבע זזה מהר מדי. נא לנסות שוב.
+
+
+ להציג הצעות חיפוש?
+
+
+ כדי לקבל הצעות, על %1$s לשלוח מה שמוקלד בשורת הכתובת אל מנוע החיפוש.
+
+
+ לא
+
+
+ כן
+
+
+ חלק ממנועי החיפוש לא יכולים להציע הצעות.
+
+
+ התעלמות
+
+
+
+
+ האתר מתנהג באופן בלתי צפוי?\n ניתן לנסות לבטל את הגנת המעקב
+
+
+ הוספה למסך הבית]]>
+
+
+ פתיחת כל קישור עם %1$s\n הגדרת %1$s כדפדפן ברירת המחדל
+
+
+ השלמה אוטומטית של כתובות עבור האתרים הנפוצים שלך\n
+יש ללחוץ לחיצה ארוכה על כתובת כלשהי בשורת הכתובת
+
+
+
+ פתיחת קישור בלשונית חדשה\n יש ללחוץ לחיצה ארוכה על כל קישור שהוא בעמוד
+
+
+ כיבוי עצות במסך ההתחלה
+
+
+ לשונית חדשה נפתחה
+
+
+ מעבר
+
+
+ עברת למצב מסך מלא
+
+
+ מעבר לקישור בלשונית החדשה מיידית
+
+
+ חסימת אתרים המעוררים חשד לסכנה או הונאה
+
+ חסימת אתרי הונאה ומתקפה, אתרי נוזקה ואתרי תכניות בלתי רצויות.
+
+
+ מצב HTTPS בלבד
+
+
+ מנסה להתחבר באופן אוטומטי לאתרים באמצעות פרוטוקול ההצפנה HTTPS לצורך אבטחה מוגברת.
+
+
+ חריגות
+
+ ביטלת את חסימת התוכן באתרים אלו.
+
+ הסרה
+
+ הסרת כל האתרים
+
+
+ חסימת עוגיות
+
+
+ האם ברצונך לחסום עוגיות?
+
+
+ הלשונית קרסה
+
+ חווינו תקלה עם הלשונית הזאת. עמך הסליחה.
+
+ בתור דפדפן פרטי, אנו לא יכולים לשמור ולשחזר את הלשונית הזאת.
+
+ סגירת לשונית
+
+
+
+
+
+ שליחת דוח קריסה אל Mozilla
+
+
+
+
+ רכיבי מעקב חסומים מאז %s
+
+ תוכן
+
+ פרסום
+
+ רשתות חברתיות
+
+ הגנת מעקב מתקדמת
+
+ ההגנות כבויות עבור אתר זה
+
+ ההגנות פעילות עבור אתר זה
+
+ החיבור מאובטח
+
+ החיבור אינו מאובטח
+
+ רכיבי מעקב ותסריטים לחסימה
+
+
+ חזרה אחורה
+
+
+
+ הסרה
+
+
+ שינוי שם
+
+ שינוי שם
+
+
+ שם קיצור הדרך
+
+
+ תמונות שמורות ומשותפות <b>לא יימחקו</b> בעת מחיקת ההיסטוריה של %1$s
+
+
+
+ ערכת נושא
+
+ בהירה
+
+ כהה
+
+ מוגדר על־ידי מצב חיסכון בסוללה
+
+ שימוש בערכת הנושא של המכשיר
+
+
+
+ אתר זה אינו תומך ב־HTTPS
+
+
+ מידע נוסף
+ ניתן לשנות הגדרה זו בהגדרות > פרטיות ואבטחה > אבטחה.]]>
+
+
+ החיבור אינו מאובטח
+
+
+
+ אם התחברת לשרת זה בהצלחה בעבר, ייתכן שהשגיאה זמנית.
+ ]]>
+
+
+ ייתכן שמישהו מנסה להתחזות לאתר, וההמשך אליו עלול להיות מסוכן.
+
+ ל־%1$s אין אמון ב־%2$s כיוון שמנפיק האישור אינו מוכר, האישור נחתם עצמאית, או שהשרת לא שולח את אישורי הביניים הנכונים.
+ ]]>
+
+
+
+ סגירת לשונית
+
+
+
+ תפסנו אותם על חם! מנענו מאתר זה לרגל אחריך. יש להקיש על המגן לקבלת מידע על מה שאנחנו חוסמים.
+
+
+ סגירת חלון קופץ
+
+
+
+ אתה מוגן!
+
+ הגדרות ברירת המחדל האלו מציעות הגנה חזקה, אבל קל לשנות את ההגדרות כדי לענות על הצרכים הספציפיים שלך.
+
+ סגירה
+
+
+ יש להקיש כאן כדי לזרוק הכל לזבל — היסטוריה, עוגיות, הכל — ולהתחיל מההתחלה בלשונית חדשה.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ סגירה
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ חיפוש יישומון
+
+ !-- This is the title of promote search widget dialog. -->
+
+ היסטוריית הגלישה נמחקה! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ניתן להתחיל בהפעלת הגלישה הפרטית שלך, ואנחנו נחסום רכיבי מעקב ושאר מרעין בישין תוך כדי.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ניתן לך להמשיך בגלישה הפרטית שלך, אבל אולי קודם כדאי להוסיף את היישומון של %1$s למסך הבית שלך לצורך הפעלה מהירה יותר.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ הוספת יישומון למסך הבית
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ יישומון נוסף למסך הבית
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ixl/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ixl/strings.xml
new file mode 100644
index 0000000000..527cade75b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ixl/strings.xml
@@ -0,0 +1,1103 @@
+
+
+
+
+
+
+
+
+ Ya\'sa
+
+ B\'a\'n kuxhtu\'
+
+ Kola
+
+
+ Chuka as moj la aaq\'ku\' ka\'t b\'ii
+
+ A\' kuxhe\' la ixaansa tib\' as eetz kuxhtu\'.\nXaanen stuul. Josq\'ii. Ka\'pu.
+
+
+ Sojsamal ve\'t unq\'a vee\' uve\' chukel kan.
+
+ Kat sotzyu unq´a vee´ chukeltu ve´t kan
+
+
+ Kat sotzyu unq´a vee´ chukeltu ve´t kan tu k´uchb´al tetz.
+
+
+ Chuka %1$s
+
+
+ Q\'ajsab\'en…
+
+
+ La aalb\'en asoj ni yan u vaa\'
+
+
+ La jaj tu %1$s
+
+
+ La jaj tu…
+
+
+ La aaq\' tu vivatze\' uve\' ti xe\'teb\'al
+
+
+ Aq´kan eexhlal uva´ jik kuxh alejata´
+
+
+ La sojsa el unq´a eexhlale´ uve´ jik kuxh alejata´
+
+ B\'an tuche\'
+ Ti\' uvaa\'
+ Lochb\'al
+ Uve\' la uch ab\'anata
+
+
+ Majel ve\'t ivatz unq\'a jitin tetze\'
+
+
+ La q´alpu u vaa´ tan la txakoni ti´ ib´anax tuchaj kam tulaj u atinb´ala´
+
+
+ Imajpu ivatz aq´on
+
+ La q´alpu unpajte aq´al uva´ la b´antuch umaj kam
+
+
+ A\' u %1$s kat cheesan
+
+
+ La jatxb\'en tuk\'
+
+
+ Sojsa kaajayil unq\'a vee\' chukel ve\'te\'
+
+
+ Jaja
+
+
+ Tzaasa\'m tuk\' oypisa\'m
+
+
+ Sojsa
+
+
+ Sotzsaj tachul unq\'a vee pich\'umal vete\'
+
+
+
+ Sojsamal as ijajpe´
+
+
+ Isojsal as ijajpu %1$s
+
+
+
+ La chuk tu Focus
+
+ La chuk tu Klar
+
+ La chuk tu Focus Beta
+
+ La chuk tu Focus Nightly
+
+
+ %1$s ni taq’ teq’ol isuuchil.
+B’anb’e u chukb’al uva’ eetz kuxhtu’:
+
+ Chuka as ti kuxh u aplicación la atin kat axh
+ La majpu ivatz unq’a chukunaal (moj la b’anax tuche’ aq’al uva’ la uch tok u chukunaal)
+ Sojsa aq’al uva’ la eesa-el u cookies as unq’a vee’ chukeltu ve’te’
+
+
+%1$s a’ u Mozilla kat aq’on. U qitz’ab’al nisa’ taq’at Internet b’a’n as jajlukan unpajte.
+Ootzi unka’te
]]>
+
+
+ Ye\'xhib\'il unka\'te la ootzin as tii tatine\'
+
+
+ Ka\'taj Kam ti\' atiisata\', cookies as tachul
+
+
+ La aaq\'kan tetz kuxhtu\', ankuxhe\' la tz\'ajsan
+
+
+
+
+ Tokeb\'al %1$s, lochb\'al
+
+
+ Ilochpu ikach´ub´al aq´al uva´ ye´ la pich´uli
+
+
+ Tuuleb\'al u web
+
+
+ Ijalpul teq\'ol tetz
+
+
+ Kaajayil
+
+
+ La aaq´kan uva´ a´ chite´ la b´anb´e, yolb´al unpajte
+
+
+ Imolpu tachul as ib\'anb\'ele\'
+
+ Chukb\'al
+
+
+ La k\'ul ka\'taj txumb\'al ti\' achukata\'
+
+ %1$s tuk aq\'on b\'en unq\'a vee\' natz\'ib\'a tu k\'uchb\'al tetze\'
+
+
+ B\'ek\'
+
+
+ Chukb´al tetz
+
+
+ Oypisa\'m
+
+
+ Tzaanale
+
+
+ Ankuxhe\' la tz\'ajsan u URL
+
+
+ Unq´a atinb´ale´ uve´ pal chit asa´ata´
+
+
+ La ooypisa u %s aq´al uva´ ankuxhe´ la b´anon tuch u 450 URLs uva´ pal chit qilata´ tulaj unq´a k´uchb´al tetze´.
+
+
+ Tetz unq´a atinb´ale´ uve´ tuk aaq´ku´
+
+
+ La ooypisa aq´al uva´ ankuxh u %s la b´anon tuch unq´a URLs uve´ ni chit asa´.
+
+
+ Tilpu isuuchil unq´a atinb´ale´
+
+
+ Tilpu isuuchil unq´a atinb´ale´
+
+
+ + Aq\' ve\'t unq\'a URL uve\' b\'anel ve\'t tuche\'
+
+
+ Tachul unq´a aq´one´ uve´ ankuxhe´ kat b´anon tuche´:
+
+
+ Aq´ku´ u URL
+
+
+ Aq\' ve\'t unq\'a URL uve\' b\'anel ve\'t tuche\'
+
+
+ Aq\' ve\'t unq\'a URL uve\' b\'anel ve\'t tuche\'
+
+
+ Aq´ku´ u chukb´al tetze´ aq´al uva´ ankuxhe´ la b´anon tuche´
+
+
+ Cookies as tachul tu tatinb\'ale\'
+
+
+ Ka\'taj tatin tachul
+
+
+ Sojsa unq\'a URLs uve\' b\'anel ve\'t tuche\'
+
+
+ Chus nimate
+
+
+ Okso\'ke as saji kam la tulb\'e itz\'ajsat je\' tib u URLs uve\' b\'anel ve\'t tuche\'.
+
+
+ URL uva\' a\'n la ookso\'k
+
+
+ Aq\'o\'ke\' moj okso\'k u URL
+
+
+ K\'uchuvatz: mozilla.org
+
+
+ K\'uchuvatz: example.com
+
+
+ Ak\' URL uva\' b\'anel ve\'t tuche\' at ve\'t ok.
+
+
+ Sojsa
+
+
+ Sojsa
+
+
+ Suchq\'en ti apich\'ut u URL uve\' kat ookso\'ke.
+
+ Yolb\'al
+
+ Iyak\'ib\'al u taq\'onb\'ale\' uve\' at kan chitu\'
+
+ Tetz kuxhtu\'
+ Maj ivatz unq\'a tiinsan tetz unq\'a vee\' ni tal tziile\'
+ At jununil unq\'a vee\' ni taltziile\' uve\' ni tiinsan unq\'a kame\' uve\' napich\'u\', kuxh ye\'xh kam nalaae\'
+ Maj ivatz unq\'a tiinsab\' tetze\' uve\' ni pich\'un
+ Ni b\'anb\'ele\' ti\' imolpe\', ipich\'ule\', isajil aq\'on Kam ikaape\' as iyikule\'
+ Maj ivatz unq\'a tiinsan tetz tenam
+ Atje\' stiib\'a tulaj ti\'aj unq\'a vee\' aq\' al uva\' la tiinsal uve\' kat apich\'u as la ik\'uch unq\'a lak\'b\'ale\' ti\' ijatxpe\'
+ Maj ivatz unq\'a tiinsan tetze\' tetz ka\'taj yol
+ Asoj la ooypisa la ib\'ane\' uva\' vale\'n kuxh la ib\'an ka\'t unq\'a ivatze\'
+ Maj ivatz unq\'a cookies
+
+
+ Ye´le, ta´ntiixh
+ Maj ivatz unq´a cookies ti´ ipich´ul ta´n ka´t uxchil
+ Maj ivatz unq\'a cookies tetz va\'len xaaol
+
+ Maj ivatz unq´a cookies uve´ at kan xo´l unq´a atinb´ale´
+ Eche´ vee´, b´an b´a´nil
+
+
+ B\'anb\'e u vi\' aq\'ab\'e\' aq\'al uva\' la jaj u aplicacione\'
+
+
+ Jaja ta’n vi’ aq’ab’ asoj pajlat kuxh kat ak’ujb’a’kan moj tul uva’ jajlu kan ve’t uma’t kam tu %s.
+
+
+ Ye\'xh kam na\'l
+
+ Muj ivatz unq\'a web tul uva\' ni jalpu unq\'a aplicaciones.
+
+ Tiib\'isal tetz
+
+ Taq\'onve\'
+ Maj ivatz unq\'a itzaaeb\'al web
+
+ La ib\'ane\' uva\' la sojsa unq\'a texhlale\' moj unq\'a vatzib\'ale\' q\'a
+
+ Bloquear JavaScript
+
+ Oora kuxh tuk ijaj unq\'a vee\' jajel kan s-a\'n, ta\'ne tan la ib\'ane\' uva\' la q\'amaxi
+
+
+ Palsa pal va navegador tu %1$s
+
+ Mozilla
+ Aq\'ven unq\'a vee\' uve\' b\'anb\'emal ve\'t s-a\'n
+
+
+ La ootzil nimal
+
+
+ U Mozilla a\' chit u b\'a\'ne\' ni chuke\' aq\'al uva\' acha\'v chit u %1$se\' la ib\'ane\'.
+
+
+ Aviso de privacidad
+
+
+ Yol atkan ti’ tu’aal
+
+
+ Tantinb’ala u’uj uve’ ni kub’anb’e
+
+
+ %s | Tatinb’al u’uj OSS
+
+
+ Tokeb\'al u %1$s
+
+
+ Taq\'onb\'al itiinsal unq\'a vee\' at ve\'t ok stuul
+
+
+ Txaakan u chukul tetze´
+
+
+ Q\'aavisab kan unq\'a chukb\'al tetze\' uve\' atik kan b\'axa
+
+
+ + Aq\'o\'k uma\'t taq\'onb\'al chukb\'al tetz
+ Sojsa unq\'a taq\'onb\'ale\' uve\' nichukube\'
+ Sojsa
+
+
+ Aq´ku´ uma´t chukul tetz
+
+ Txaakan u chukul eetze´ uve´ pal chit asa´ata´:
+
+
+ Aq\'o\'k uma\'t aq\'onb\'al tetz chukb\'al tetz
+
+ Ib\'ii u taq\'onb\'al chukul tetze\'
+ Chuk qinib tetz ti\' iba\'nb\'ele\'
+ Kola
+
+
+ K\'uchuvatz: example.com/search/?q=%s
+
+ Ak\' aq\'onb\'al ti\' u chukb\'al tetze\' uve\' at ve\'t kan.
+
+ La aaq\'ku\' ib\'ii u aq\'onb\'ale\' uve\' la xaansan axh stuul
+ Uma\'l aq\'onb\'al uva\' la chukun u vee\' at ve\'t kan as a\' u b\'iie\' ni b\'anb\'e ve\'te\'.
+
+ Aq\'ku\' u qinib\' chukb\'al tetze\'
+
+ La pich\'u b\'axa asoj ni lejtib\' qinib\' chukb\'al tetze\' tuk\' u k\'uchuvatze\'
+
+
+ La josq\'i unq\'a vee\' chukel ve\'t kan
+
+
+ Ye\' kuxh eetz sti\'
+
+
+ Sojsa el unq\'a vee\' uve\' chukelaj ve\'te\'
+
+
+ Unq\'a vee\' jajlu kan kuxhtu\': %1$s
+
+
+ Ile\' b\'a\'n kuxh toke\'
+
+
+ Ile\' ni teq\'o ku\'tzan
+
+
+ Il u tatinb\'al web ni teq\'o ku\'tzan
+
+
+ Ka\'t tuuleb\'al
+
+
+ Exhlal ti´ achukat unka´te
+
+
+ La b\'en axh svatz
+
+
+ La eeq\'o ku\'tzan tatinb\'al u web
+
+
+ Q\'aaven tu vee\' ni tiinsal kat
+
+
+ Aaq\'kan kuxh u webe\' aq\'al la teq\'o ku\'tzan
+
+
+ La q\'aav axh tu aplicación atik ti\' iqul
+
+
+ Tachul unq\'a vee\' majel ve\'t ivatz
+
+
+ Maj ivatz unq\'a tiinsan tetze\'
+
+ Unq\'a vee\' la uch ab\'anata\'
+
+ La jaj u vaa\' tu uma\'t aplicación
+
+ La uch eel ch\'u\'l tu %1$s aq\'al la jaj u vaa\' tu %2$s.
+
+ La chuk uma\'l aplicación uva\' la jajon u vaa\'
+
+ Ni kuxh umaj unq\'a aplicacione\' la jajon u vaa\' tu va molob\'ale\'. La uch eel ch\'u\'l tu %1$s aq\'al uva\' la uch a jajat uma\'te uva\' ni lej tib\' tik\' u %2$se\'.
+
+ La inch tel ch\'u\'l tu xaansab\' tetze\' uve\' tetz kuxhtu\'?
+
+
+ Ile\' kat tzojpiy u %1$se\' sti\'
+
+
+ Jaja
+
+
+
+
+
+
+
+
+
+ Ye\' kat ilej umaj jajon tetz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jupa
+
+
+
+ B´a´n ool tu %1$s
+
+
+ Oora kuxhtu´. Eetz kuxhtu´. Ye´l uma´l b´alonaal ati.
+
+
+ Xe´ten
+
+
+
+
+ %1$s jit eela tuk´ ka´t unq´a chukb´ale´
+
+
+ Tuk kusojsa unq´a vee´ chukeltu ve´t s-a´n aq´al uva´ ye´xhib´il la pich´un.
+
+
+
+
+ Aq´kan u %1$s uva´ a´ kuxhe´ la b´anb´e aq´al uva´ ye´xhib´il la ilon kam napich´u.
+
+
+
+ Aq´kan uva´ a´ kuxhe´ la b´anb´e
+
+
+
+ Ye\' ookeb\'al sti\'
+
+
+ B\'an ooksat uyak\'il u eetze\'
+
+ Eq\'o u tiinsab\' tetze\' tu uma\'t tanul. Maj ivatz unq\'a altziinaj kame\' as ka\'taj kam uve\' ye\' ni eq\'on ku\'tzan u vee\' nasa\' apich\'uta\'.
+
+
+ Achukune\', uve\' nasa\'
+
+ Umaj kam nachuke\' uva\' va\'len? Txaa uma\'t ch\'ich\' tetz chuku\'m uva\' nib\'anax kat tuch.
+
+
+ Aq\'kan unq\'a kame\' vatz va ch\'ich\'e\' uva\' palchit itxakone\' see
+
+ Q\'aaven oorimal tu unq\'a kame\' uve\' aamachit a achukat tu %1$se\'. Txaa \"oksaloj ok ti xe\'teb\'al\" vatz u %1$se\'.
+
+
+ Chaansa see uva\' axh kuxh la ilon
+
+ A\'vet kan u %1$se\' uva\' a\'chite\' la tiinsan vete\' as b\'anb\'e unq\'a vi b\'a\'nil u tiinsab\' tetze\' tul uva\' la jaje\' tu ka\'ta\'j aplicación.
+
+ Palyin stuul!
+ Ye\' ookeb\'al sti\'
+ Uma\'te\'
+
+
+ -
+
+
+ Aq\'okej
+
+
+ ANCHITE’
+
+
+ Ya\'sa
+
+
+ YE’LE
+
+
+ Uve’ k’ujb’a’mal kan tuk ijaje’ tuk’ u kach’ub’al tetze’ aq’al uva’ ye’xhib’il kuxh la kanon
+
+
+ Axh kuxh la b\'anb\'en
+
+
+ Unq\'a vaa\' neele\' la isojsa u %1$se asoj la kane\'. Jit chit tz\'ajinale\' la jaj u aplicación moj la eel kam ni b\'an u tiinsab\' tetze\'.
+
+
+ Sojsa kaajayil unq\'a vee\' kat achukla
+
+
+ Eq\'o ku\'tzan u Firefoxe
+
+
+
+
+
+
+
+
+ kam yol xo’l tenam ti u Mozilla at sti’ as atkan unka’ta yol ti’ unq’a lochb’al tetz uva’ jajlu kan.]]>
+
+
+ tzitza’.]]>
+
+
+ unq’a tu’aale’ uve’ jajlu kan kuxhtu’.]]>
+
+
+ GNU General Public License v3, as ile’ at ve’t tzitza’ .]]>
+
+
+ Ab\'ii la ooksa kan
+ Exhlal axh kuxh la ootzin
+ Josq\'i
+
+
+
+ Ile\' b\'a\'n ve\'t tatine\'
+ Ye\'xh kam b\'a\'n koj tatine\'
+
+ Ile\' pich\'umal ve\'te\' ta\'n: %1$s
+
+
+ Kach\'ub\'al tatine\'
+ At ve\'t u URL
+
+
+ La chuk tu u vee\' jajle\'l s-a\'n
+
+
+ La chuk tu u vee\' jajle\'l s-a\'n
+
+
+ %1$d/%2$d
+
+ %1$d ti\' %2$d
+
+
+ Chuk u vee\' a\'n la leje\' svatz b\'en
+
+ Chuk u vee\' lejel kan b\'axa
+
+ Jup u vee\' nachuke\'
+
+
+
+
+ Jaj aq\'al uva\' la eeq\'otzan svatz
+
+
+ Atinb’al tetz ilb’al ivatz
+
+
+ URL eq\'omal ve\'t kan ivatz
+
+
+ Aq\'onb\'al ti\' ib\'anax tuche\'
+
+
+ La jaj tatinb’al unq’a aplicaciones
+
+
+ Tii ve\'t tuche\'
+
+
+ Okeb’al tulaj unq’a atinb’ale’
+
+
+ Iku’ch’u’l ich’ii Banner tetz Cookies
+
+
+ Oypisa\'m
+
+
+ Tzaanale
+
+
+ Iku’ch’u’l ich’ii Banner tetz Cookies
+
+
+ Ye’ nimal banners la eele’ ankuxhe’ tuk eesanel unq’a cookies tul uva’ la uchi.
+
+ -->
+ Iku’ch’u’l ich’ii Banner tetz Cookies
+
+
+ Oypisamal ve’t kan tetz u atinb’ala’ vaa’
+
+
+ U atinb’ale’ ye’ la uch ab’anb’el tu jalte
+
+
+ Tzaasamal ve’t kan tetz u atinb’ala’ vaa’
+
+
+ Iku’ch’u’l ich’ii Banner tetz Cookies
+
+
+ Tzaasamal ve’t kan tetz u atinb’ala’ vaa’
+
+
+ Oypisamal ve’t kan tetz u atinb’ala’ vaa’
+
+
+ Oypisa iku’ch’u’l yol tetz Cookies ti’ %1$s?
+
+
+ Tzaasa iku’ch’u’l yol tetz Cookies ti’ %1$s?
+
+
+ U %1$s tuk osojsa unq’a cookies tzitza’ as tuk ib’antuch u u’uje’. Asoj la sojsal-el kaajayil unq’a cookies la ib’ane’ uva’ la ijup uve’ naaq’om kat moj la teese’l unq’a kame’ uve’ la loq’iki.
+
+
+ %1$s la ib’ane’ uva’ ankuxhe’ ye’ la k’ulun unq’a cookies.
+
+
+ Cheel ve’te’, u atinb’ale’ vaa’ ye’ la uch tilat tib’ tuk’ iku’ch’u’l u banner tetz u cookies. Nasa’ uva’ ajajat te unq’a kumoole’ aq’al la pich’uli as la taq’ku’ iyak’ib’al svatz b’en?
+
+
+ Ya’sakan
+
+
+ La jaj iyak’ib’al tetz
+
+
+ Ijajpu u yak’ib’al tetze’ ile’ kat b’enya.
+
+
+ Ijajpu u yak’ib’al tetze’ ile’ kat b’enya.
+
+
+
+ %1$s tuk imaje’l ivatz unq’a cookies uve’ ni kuxh ilaao’k tib’.\n\nSaji as la txaae’ ab’iste unq’a banners tetz cookies nasa’ tu %2$s.
+
+ b\'an tuche\'
+
+
+ Ankuxhe’ la toypisa tib’
+
+
+ Aq’al uva’ la uchi:
+
+
+ 1. La oon axh uve’ ni b’anax kat tuch tetz Android
+
+
+ La uch kat]]>
+
+
+ La b’en axh uve’ la b’anax kat tuche’
+
+
+ %1$s]]>
+
+
+ Eesan vatzib’al
+
+
+ Alb’al yol
+
+
+ Atinb’al at kat axh cheel
+
+
+ Aq’b’al nachb’al
+
+
+ Aq’on uva’ ni til u DRM
+
+
+ La jaj ooke’
+
+
+ Majel ve´t ivatz
+
+
+ K´ulel ve´te´
+
+
+ Majel ivatz ta’n u Android
+
+
+ La uch aaq’at tuul avi’ as eelpe’ unpasteurized
+
+
+ A’ kuxh u tuul vi’e’ la maje’
+
+
+ A’e’ b’a’n la b’anb’e
+
+
+ La maj ivatz tuul avi’ as eelpe’ unpajte
+
+
+ Chusb’al
+
+
+ U Firefox la uch taq’at kan as ib’anax chusb’al tul uva’ la uchi.
+
+
+ Ootzi ka´te
+
+
+ La uch ijupata’ aq’al uva’ la jalpu unq’a kame’ stuul
+
+
+ Sojsa el
+
+
+ Ile’ b’a’n ve’te’
+
+
+ Kat tzojpiya
+
+
+ Eesamal ve\'t el ta\'n USB/Wi-Fi
+
+
+ Jaj ivatz
+
+
+ Ala uva’ a’ u vi’ aq’ab’e’ la b’anb’e
+
+
+ La uch ab’anb’et u vi’ aq’ab’e’ aq’al uva’ la yakeb’ axh ti’ ab’anb’et u vaa’.
+
+
+ La jaj uma’t atinb’al aq’al uva’ la xe’t axh aq’onvoj tu uma’t ak’ atinb’al
+
+
+ Texhlal u vi\' q\'ab\'e\'
+
+
+ Ye\' ni texhla u vi\' aq\'ab\'e\'. B\'an unpajte.
+
+
+ Oora kuxh kat atiinsa u vi\' aq\'ab\'e\'. B\'an unpajte.
+
+
+ La ik’uch ka’t ichukb’al tetz?
+
+
+ Aq’al uva’ la k’ul lochb’al, %1$s nisa’ taq’at b’en jank’al chit unq’a vee’ natz’ib’a tu chukb’al tetze’.
+
+
+ Ye\'le
+
+
+ Kanoj
+
+
+ At jununil unq\'a taq\'onb\'ale\' ye\' ni k\'uchun u nachonsal tetze\'.
+
+
+ Ye\' kuxh eetz sti\'
+
+
+
+
+ Ni kuxh iyane\'?\n Eesa u kach\'ub\' chukb\'al tetze\'
+
+
+ Aq\'kan ti xe\'teb\'al]]>
+
+
+ Jaj Junun u k\'uchb\'al chukb\'al tetze\' tu %1$s\n Aq\'kan u %1$se\' uva\' a\' chite\' la b\'anb\'e
+
+
+ B\'an atz\'ajsat unq\'a URLs ti\' unq\'a vee\' uve\' pal chit ab\'anb\'eta\'\n Laaku\' kam kuxh URL tan u k\'uchb\'al ixaansal tetze\'
+
+
+ Jaj uma\'t ak\' chukb\'al tetz\n Laaku\' ab\'iste kuxh chukb\'al tetz
+
+
+ Jupkan unq’a alb’al txumb’ale’ uve’ ni tul svatz
+
+
+ Ak’ ilb’al tetz jajle’le
+
+
+ Jalpu
+
+
+ La eel kaajayil chitu’
+
+
+ Oora kuxh la jalpu tatinb’al tu uma’t ilb’al tetz
+
+
+ Maj ivatz unq’a va’lexh la kame’ uve’ ni yansane’
+
+ Maj ivatz unq’a vee’ xochel ve’t b’en, echkole’ u malware as unq’a tetz u software uva’ ye’ ni txakone’.
+
+
+ A’ luxh u ilb’al HTTPS
+
+ Ankuxhe’ ni tal tok tulaj unq’a atinb’ale’ uve’ ni b’ab’e teq’ol u majol ivatze’ HTTPS aq’al uva’ tii tatine’.
+
+
+ A’ kuxh unq’a vi’la’
+
+
+ Kat aq’alpul u majol tetz unq’a aq’one’ uve’ atik tzitza’.
+
+ Sojsa
+
+ Sojsa-el kaajayil unq’a atinb’ale’
+
+
+ Maj ivatz unq’a cookies
+
+
+ Nasa’ la maj ivatz unq’a cookies?
+
+
+ Kat yanyu u k’uchb’al tetze’
+
+ La kuy o’. At uma’l kam uva’ ni yansan u k’uchb’al tetze’.
+
+ U atinb’ale’ uva’ eetz kuxh naaq’onvu stuul, ye’ kat kukola ni kuxh la uch kuq’aavisat u k’uchb’al tetze’.
+
+
+ Jup u ilb’al tetze’
+
+
+ La aalb’en tu Mozilla ti’ unq’a vee’ ni Yáñez’
+
+
+
+
+ Chukulaj tetz majel ivatz ta’n %s
+
+ Tuuleb’al
+
+ Altziinaj kam
+
+ Tetz tenam
+
+ Pich’ul tetz
+
+ B’anel ve’t tuche imajax ivatz vatz u chukunaale’
+
+ Eesamal ve’t el u lochb’al tetze’ tu atinb’ala’
+
+ Unq’a lochb’al tetze’ ile’ oypinal ve’te’ tzitza’
+
+ Ile’ b’a’n kuxh taq’onve’
+
+ Ye’xh kam b’a’n koj taq’onve’
+
+ Chukunaal tuk’ scripts la majpu ivatz
+
+
+ Q’aaven unpajte
+
+
+
+ Sojsa
+
+ Jalpu ib’ii unpajte
+
+ Jalpu ib’ii unpajte
+
+ Ib’ii u aq’one’ uve’ k’ujb’a’mal kan s-a’n
+
+
+ Jank’al unq’a vatzib’ale’ uve’ kolel as paxsamal unpajte <b>ye’xh kam</b> la mox sojsal-el tul uva’ la sojsa-el unq’a vee’ chukel tu %1$s
+
+
+
+ Tilb’al
+
+ Saj
+
+ Tokixi
+
+
+ At ve’t kan chitu’ ta’n ilochol taq’ax iyak’il xamal
+
+ La b’anb’el u ilb’al tetze’ tu ch’ich’e’ vaa’
+
+
+ U atinb’ale’ vaa’ ye’ la uch ib’en tu HTTPS
+
+
+ La ootzi ka’te
+ Jalpu u vaa’ uve’ ni b’anax kat tuche’ > Eetz kuxhtu’ & Kach’ub’al tetz > Kach’ub’al tetz.]]>
+
+
+ Ye’xh kam b’a’n koj taq’onve’
+
+
+
+ Asoj b’a’nik kuxh tatin u vaa’ b’axa, u vaa’ neel cheel tuk kuxh paloje’.
+ ]]>
+
+
+ At umaj uxchil ni tokch’u’l ib’anat ivatz u atinb’ale’ vaa’ asoj la yakeb’ axh at iva’laxhil.
+
+ %1$s ye’ ni k’ujb’a’ ik’u’l ti’ u %2$s tan ye’ ootzimal ab’il ni aq’onvan, as ankuxhe’ ni b’anb’ela je’ tib’ as ye’ ni taq’ tzan unq’a u’uje’ ti jikil.
+]]>
+
+
+
+ Jup u ilb’al tetze’
+
+
+
+ B’a’ne’! Aq’al uva’ ye’l axh li ch’itpi’li axh u vaa’. Laa ku’ u tal texhlale’ aq’al la eele’ ab’iste unq’a vee’ ni kumaj ivatz.
+
+
+ La juplu uma’l kam uva’ ni kuxh ichee’
+
+
+
+ Il axh b’anel ooche’!
+
+
+ U b’anb’al tuche’ vaa’ uve’ at kan chitu’, ni taq’ uma’l tiib’al tatine’. Oora kuxh la b’an tuche’ aq’al uva’ la iloch axh.
+
+
+ Ya’sakan
+
+
+ Laa tzitz’ aq’al uva’ la sojsa kaajayil — unq’a vee’ chukel, cookies as tuk’ ka’t unq’a vee’ — as xe’ten tu uma’t ak’ ilb’al.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Jupa
+
+
+ Widget tetz chukunaal
+
+
+ Kay sotzyu tuuleb’al unq’a aq’on uve’ kat chukpi! 🎉
+
+
+ Xe’tisa u aq’one’ tu atinb’ale’ uve’ eetz kuxhtu’ as tuk kumaj ivatz unq’a b’anol va’lexhe’ tul naaq’onve’.
+
+
+ Tuk qaq’kan axh uva’ axh kuxh la atin axh tzitza´, oora kuxh ab’ana tuk u widget %1$s tu xe’teb’al u aaq’one’.
+
+
+ La aaq’ku’ u widget ti xe’teb’al aq’on
+
+
+ Widget ile’ at ve’t ku’ ti xe’teb’al aq’on
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ja/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000000..e3008be1ca
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ja/strings.xml
@@ -0,0 +1,1120 @@
+
+
+
+
+
+
+
+
+ キャンセル
+
+ OK
+
+ 保存
+
+
+ 検索語またはアドレスを入力
+
+ プライベートブラウジングを自動的に。\nブラウズ、消去の繰り返し。
+
+
+ 閲覧履歴が消去されました。
+ 閲覧履歴を消去しました
+
+
+ タブの閲覧履歴が消去されました。
+
+
+ %1$s を検索
+
+
+ 共有...
+
+
+ サイトの問題を報告
+
+
+ %1$s で開く
+
+
+ 外部アプリで開く…
+
+
+ ホーム画面に追加
+
+
+ ショートカットを追加
+
+ ショートカットから削除
+
+
+ 設定
+ 製品情報
+ ヘルプ
+ あなたの権利
+
+
+ ブロックされた追跡
+
+
+ これをオフにすることにより、一部のサイトの問題が解決する可能性があります
+
+
+ コンテンツブロッキング
+
+ サイトの問題を解決するにはオフにしてください
+
+
+ Powered by %1$s
+
+
+ 共有方法を選択
+
+ 閲覧履歴を消去しますか?
+ この通知をタップして閲覧履歴を安全に消去してください。
+
+
+ この通知をタップまたはスワイプして閲覧履歴を安全に消去してください。
+
+ 閲覧履歴を消去
+
+
+ 開く
+
+
+ 消去して開く
+
+
+ 消去
+
+
+ 閲覧履歴を消去
+
+
+
+ 消去して開く
+
+
+ 消去して %1$s を開く
+
+
+
+ Focus で検索
+
+ Klar で検索
+
+ Focus Beta で検索
+
+ Focus Nightly で検索
+
+
+ %1$s ではあなたがすべてをコントロールできます。
+次のようにプライベートブラウザーとして使えます。
+
+ アプリで適切に検索、閲覧
+ トラッカーをブロック (あるいは設定を更新して許可)
+ 検索履歴、閲覧履歴の消去だけでなく Cookie を削除
+
+
+%1$s は Mozilla が開発しています。私たちのミッションは健全で開かれたインターネットを発展させることです。
+詳しくはこちら
]]>
+
+
+ プライバシーとセキュリティ
+
+
+ トラッキング、Cookie、データの選択
+
+
+ 既定、自動補完を設定
+
+
+
+
+ %1$s について、ヘルプ
+
+
+ 強化型トラッキング防止
+
+
+ ウェブコンテンツ
+
+
+ アプリの切り替え
+
+
+ 一般
+
+
+ 既定のブラウザー、言語
+
+
+ データの収集と使用
+
+ 検索
+
+
+ 検索候補を取得
+
+ %1$s はあなたがアドレスバーに入力した語句を検索エンジンへ送信します
+
+
+ 既定
+
+
+ 検索エンジン
+
+
+ オン
+
+
+ オフ
+
+
+ URL 自動補完
+
+
+ 人気サイト
+
+
+ 有効にすると、%s のアドレスバーで 450 以上の人気の URL が自動補完されます。
+
+
+ 追加したサイト
+
+
+ %s によるお気に入りの URL の自動補完を有効にします。
+
+
+ サイトの管理
+
+
+ サイトの管理
+
+
+ + カスタム URL を追加
+
+
+ 自動補完リスト:
+
+
+ URL を追加
+
+
+ カスタム URL を追加
+
+
+ カスタム URL を追加
+
+
+ リンクを自動補完に追加
+
+
+ Cookie とサイトデータ
+
+
+ データの選択
+
+
+ カスタム URL を削除
+
+
+ 詳細情報
+
+
+ カスタム自動補完 URL の追加と管理。
+
+
+ 追加する URL
+
+
+ URL を入力するか貼り付けてください
+
+
+ 例: mozilla.org
+
+
+ 入力例: example.com
+
+
+ 新しいカスタム URL を追加しました。
+
+
+ 削除
+
+
+ 削除
+
+
+ 入力した URL をよく確認してください。
+
+ 言語
+
+ システム標準
+
+ プライバシー
+ 追跡広告をブロック
+ 一部の広告は、あなたがその広告をクリックしなくても訪れたサイトを追跡します
+ アクセス解析をブロック
+ タップやスクロールなどの操作統計を収集、解析、計測するために利用されます
+ ソーシャル追跡をブロック
+ ユーザーの訪問を追跡するためサイトに埋め込まれ、共有ボタンのように表示されます
+ 他の追跡コンテンツをブロック
+ 有効にすると、ページによっては予期せぬ動作をする原因になります
+ Cookie をブロック
+
+
+ いいえ
+ サードパーティの追跡 Cookie のみブロック
+ サードパーティ Cookie のみブロック
+
+ クロスサイト Cookie をブロック
+ はい
+
+
+ アプリのロック解除に指紋を使用
+
+
+ ショートカットを追加した場合やウェブサイトがすでに %s で開いている場合、指紋認証を使用してロックを解除します。
+
+
+ ステルス
+
+ アプリ切り替え時にウェブページを隠し、スクリーンショット撮影を禁止します。
+
+ セキュリティ
+
+ パフォーマンス
+ ウェブフォントをブロック
+
+ アイコンや画像が正常に表示されない場合があります
+
+ JavaScript をブロック
+
+ ページの読み込みが速くなる可能性がありますが、予期せぬ動作をする可能性もあります
+
+
+ %1$s を既定のブラウザーに設定
+
+ Mozilla
+ 使用状況データを送信
+
+
+ 詳細情報
+
+
+ Mozilla は %1$s を皆さんに提供し改善するために必要な情報だけを収集するよう努めています。
+
+
+ プライバシー通知
+
+
+ ライセンス情報
+
+
+ 利用しているライブラリー
+
+
+ %s | OSS ライブラリー
+
+
+ %1$s について
+
+
+ 選択可能な検索エンジン
+
+
+ 検索エンジンの選択
+
+
+ 既定の検索エンジンを復元
+
+
+ + 別の検索エンジンを追加
+ 検索エンジンを削除
+ 削除
+
+
+ 別の検索エンジンを追加
+
+ ご希望の検索エンジンを選択してください:
+
+
+ 検索エンジンを追加
+
+ 検索エンジン名
+ 使用する検索語
+ 保存
+
+
+ 入力例: example.com/search/?q=%s
+
+ 新しい検索エンジンが追加されました。
+
+ 検索エンジン名を入力
+ 同名の検索エンジンがすでにインストールされています。
+
+ 検索語を入力
+
+ 検索語が入力例の書式とマッチしているか確認してください
+
+
+ 入力内容を消去
+
+
+ 閉じる
+
+
+ 閲覧履歴を消去
+
+
+ 開かれているタブの数: %1$s
+
+
+ 安全な接続
+
+
+ 読み込み中
+
+
+ ウェブサイト読み込み完了
+
+
+ その他のオプション
+
+
+ その他のオプションボタン
+
+
+ 次のページへ進む
+
+
+ ウェブサイトを再読み込み
+
+
+ 前のページへ戻る
+
+
+ サイトの読み込みを中止
+
+
+ 前のアプリへ戻る
+
+
+ ブロックされた追跡数
+
+
+ オンライン追跡をブロック
+
+ あなたの権利
+
+ リンクを外部アプリで開く
+
+ %1$s を離れてこのリンクを %2$s で開くことができます。
+
+ リンクを開けるアプリを探す
+
+ お使いの端末にはこのリンクを開けるアプリがありません。%1$s を離れて、開けるアプリを %2$s で探すことができます。
+
+ プライベートブラウジングを終了しますか?
+
+
+ %1$s 完了
+
+
+ 開く
+
+
+
+
+
+
+
+
+
+
+ ショートカットに追加しました
+
+ サーバーが見つかりませんでした
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 閉じる
+
+
+
+ %1$s へようこそ
+
+
+ 高速でプライベート。気を散らすものはありません。
+
+
+ はじめる
+
+
+
+ %1$s は他のブラウザーとは一味違います
+
+
+ プライバシー強化のため、アプリを閉じると履歴が消去されます。
+
+
+
+ %1$s を既定のブラウザーに設定すれば、開いたリンク先であなたのデータが保護されます。
+
+
+ 既定のブラウザーに設定する
+
+
+ スキップ
+
+
+
+ プライバシーを強化
+
+ プライベートブラウジングを次のレベルへと進めましょう。サイトを越えてあなたを追跡したり、ページの読み込みを遅くしたりする可能性のある広告その他のコンテンツを自動的にブロック。
+
+
+ 好きな方法で検索
+
+ 何か違ったものを探していますか? 設定で別の検索エンジンを既定として選んでみましょう。
+
+
+ ホーム画面にショートカットを追加
+
+ %1$s でもお気に入りのサイトへ素早く戻れます。%1$s のメニューから「ホーム画面に追加」を選択するだけ。
+
+
+ プライバシーを習慣に
+
+ %1$s を既定のブラウザーに設定して、他のアプリからウェブページを開いたときに、プライベートブラウジングの恩恵を受けましょう。
+
+ 了解
+ スキップ
+ 次へ
+
+
+ -
+
+
+ 追加
+
+
+ はい
+
+
+ キャンセル
+
+
+ いいえ
+
+
+ ショートカットは強化型トラッキング防止が無効の状態で開かれます
+
+
+ プライベートブラウジングセッション
+
+
+ %1$s のセッションは通知をワンタップするだけで消去できます。アプリを開いたり、ブラウザー内でどのページが表示されているか確認したりする必要はありません。
+
+
+ 閲覧履歴を消去
+
+
+ Firefox をダウンロード
+
+
+
+
+
+
+
+
+ Mozilla Public License およびその他オープンソースライセンスの条件に基づいて使用することができます。]]>
+
+
+ Mozilla 商標ポリシー をご覧ください。]]>
+
+
+ ライセンス に基づいて公開されています。]]>
+
+
+ GNU General Public License v3 の下で単独のリソースとして使用しており、それらは こちら から入手可能です。]]>
+
+
+ ユーザー名
+ パスワード
+ 消去
+
+
+
+ 安全な接続
+ 安全でない接続
+
+ 認証局: %1$s
+
+
+ サイトセキュリティ
+ URL は既に存在します
+
+
+ ページ内検索
+
+
+ ページ内検索
+
+
+ %1$d/%2$d
+
+ %2$d 件中 %1$d 件目
+
+
+ 次の結果を検索
+
+ 前の結果を検索
+
+ ページ内検索を閉じる
+
+
+
+
+ デスクトップ版を見る
+
+
+ デスクトップサイト
+
+
+ URL をコピーしました
+
+
+ 開発者ツール
+
+
+ リンクを外部アプリで開く
+
+
+ 詳細
+
+
+ サイトの許可設定
+
+
+ Cookie バナーの削減
+
+
+ オン
+
+
+ オフ
+
+
+ Cookie バナーの削減
+
+
+ Cookie 同意確認を自動的に拒否することで、表示されるバナーを可能な限り減らします。
+
+ -->
+ Cookie バナーの削減
+
+
+ このサイトでオン
+
+
+ 現在サポートされていないサイトです
+
+
+ このサイトでオフ
+
+
+ Cookie バナーの削減
+
+
+ このサイトでオフ
+
+
+ このサイトでオン
+
+
+ %1$s で Cookie バナー削減を有効にしますか?
+
+
+ %1$s で Cookie バナー削減を無効にしますか?
+
+
+ %1$s はこのサイトの Cookie を消去してページを読み込み直します。すべての Cookie を消去すると、ログアウトしたり、ショッピングカートが空になったりする場合があります。
+
+
+ %1$s は自動的に Cookie 要求を拒否することができます。
+
+
+ 現在、このサイトは Cookie バナー削減機能に対応していません。このウェブサイトのレビューを開発チームに依頼して、サポートを追加したいですか?
+
+
+ キャンセル
+
+
+ サポートをリクエスト
+
+
+ サポートサイトへのリクエストが送信されました。
+
+
+ サポートサイトへのリクエストが送信されました。
+
+
+
+ %1$s は、迷惑な Cookie バナーを閉じるために Cookie 同意確認を拒否しようとします。\n\n%2$sで Cookie バナーの動作を管理します。
+
+
+ 設定
+
+
+ 自動再生
+
+
+ 許可するには:
+
+
+ 1. Android の設定を開きます
+
+
+ 権限 をタップします]]>
+
+
+ 設定を開く
+
+
+ %1$s をオンに切り替えます]]>
+
+
+ カメラ
+
+
+ マイク
+
+
+ 位置情報
+
+
+ 通知
+
+
+ DRM 制御されたコンテンツ
+
+
+ 許可を求める
+
+
+ ブロック済み
+
+
+ 許可済み
+
+
+ Android によってブロック
+
+
+ 音声と動画を許可
+
+
+ 音声のみブロック
+
+
+ おすすめ
+
+
+ 音声と動画をブロック
+
+
+ 調査
+
+
+ Firefox が調査をインストールして実行することがあります。
+
+
+ 詳細情報
+
+
+ 変更を適用するためアプリケーションを終了します
+
+
+ 削除
+
+
+ 有効
+
+
+ 完了
+
+
+ USB/Wi-Fi を通じたリモートデバッグ
+
+
+ ロック解除
+
+
+ 指紋認証で確認
+
+
+ 指紋認証を使用して現在のアプリセッションを続行できます。
+
+
+ リンクを新しいセッションで開く
+
+
+ 指紋アイコン
+
+
+ 指紋を認識できませんでした。もう一度試してください。
+
+
+ 指の動きが素早すぎます。もう一度試してください。
+
+
+ 検索候補を表示しますか?
+
+
+ 検索候補を取得するには、アドレスバーに入力した内容を %1$s が検索エンジンへ送信する必要があります。
+
+
+ いいえ
+
+
+ はい
+
+
+ 一部の検索エンジンは候補表示に対応していません。
+
+
+ 閉じる
+
+
+
+
+ サイトが予期せぬ挙動をしますか?\n トラッキング防止機能を無効化してみてください
+
+
+ ホーム画面に追加]]>
+
+
+ すべてのリンクを %1$s で開くには\n %1$s を既定のブラウザーに設定してください
+
+
+ よく使うサイトで URL を自動補完するには\n アドレスバー内で URL を長押しします
+
+
+ リンクを新しいタブで開くには\n ページ上でリンクを長押しします
+
+
+ スタート画面上のヒント表示をオフにする
+
+
+ 新しいタブを開きました
+
+
+ 切り替え
+
+
+ 全画面表示モードです
+
+
+ 新しいタブ内のリンクへすぐに切り替えます
+
+
+ 潜在的に危険な詐欺サイトをブロック
+
+ 報告された詐欺サイトや攻撃サイト、マルウェアサイト、不要なソフトウェアをインストールさせようとするサイトをブロックします。
+
+
+ HTTPS-Only モード
+
+
+ セキュリティ強化のため、自動的に HTTPS 暗号化プロトコルを使用してサイトへの接続を試行します。
+
+
+ 例外
+
+ これらのサイトではコンテンツブロッキングを無効にします。
+
+ 削除
+
+ すべてのウェブサイトを削除
+
+
+ Cookie をブロック
+
+
+ Cookie をブロックしたいですか?
+
+
+ タブがクラッシュしました
+
+ 申し訳ありません。このタブに問題があります。
+
+ プライベートブラウザーとして、このタブは一切保存されず、復元もできません。
+
+ タブを閉じる
+
+
+
+
+
+ クラッシュレポートを Mozilla へ送信する
+
+
+
+
+ %s 以降にブロックされたトラッカー
+
+ コンテンツ
+
+ 広告
+
+ ソーシャル
+
+ 解析
+
+ 強化型トラッキング防止機能
+
+ このサイトでは保護が無効になっています
+
+ このサイトでは保護が有効になっています
+
+ 接続は安全です
+
+ 接続は安全ではありません
+
+ ブロックするトラッカーとスクリプト
+
+
+ 戻る
+
+
+
+ 削除
+
+
+ タイトル変更
+
+ タイトル変更
+
+
+ ショートカット名
+
+
+ 保存、共有された画像は %1$s の履歴を消去しても <b>削除されません</b>。
+
+
+
+ テーマ
+
+ Light
+
+ Dark
+
+ バッテリーセーバーで設定
+
+ 端末のテーマに従う
+
+
+
+ このサイトは HTTPS に対応していません
+
+
+ 詳細情報
+ この設定は、[設定] > [プライバシーとセキュリティ] > [セキュリティ] から変更できます。]]>
+
+
+ 安全でない接続
+
+
+
+ 以前は正常に接続できていた場合、この問題は恐らく一時的なものですので、後で再度試してみてください。
+ ]]>
+
+
+ 誰かがサイトになりすまそうとしている可能性があるため、この先へ進むと危険です。
+
+ 証明書の発行者が不明か、証明書が自己署名されているか、サーバーが正しい中間証明書を送信していないため、%1$s は %2$s を信頼できません。
+ ]]>
+
+
+
+ タブを閉じる
+
+
+
+ このサイトによるスパイ行為を阻止しました。ブロックしたものに関する情報を見るには盾アイコンをタップしてください。
+
+
+ ポップアップを閉じる
+
+
+
+ 保護されています
+
+ これらの既定の設定は強力な保護を提供します。必要に応じて、簡単に設定を調整できます。
+
+ 閉じる
+
+
+ ここをタップして、履歴、Cookie、すべて破棄し、新しいタブで新たに始めましょう。
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ 閉じる
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ 検索ウィジェット
+
+ !-- This is the title of promote search widget dialog. -->
+
+ 閲覧履歴を消去しました🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ プライベートブラウジングセッションを開始すると、トラッカーやその他の迷惑なものをブロックします。
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ プライベート ブラウジングはお任せしますが、次回はホーム画面の %1$s ウィジェットですばやく開始できます。
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ ホーム画面にウィジェットを追加
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ ホーム画面にウィジェットを追加しました
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-jv/strings.xml b/mobile/android/focus-android/app/src/main/res/values-jv/strings.xml
new file mode 100644
index 0000000000..65048ad799
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-jv/strings.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+ Batal
+ OKÉ
+
+ Simpen
+
+ Golèk utawa lebokaké alamat
+
+ Dlajahan pribadi otomatis.\nDlajah. Busak. Balèni.
+
+ Riwayat dlajahan sampéyan wis dibusak.
+
+ Golèk %1$s
+
+ Dumké…
+
+ Lapur Masalah Situs
+
+ Bukak ing %1$s
+
+ Bukak ing…
+
+ Tambah ing Layar Utama
+
+ Setèlan
+ Ngenani
+ Pitulung
+ Hak Sampéyan
+
+ Palacak kablokir
+
+ Matèni iki bisa ndandani sawetara masalah situs
+
+ Blokir kontèn
+ Patèni kanggo ndandani sawetara situs
+
+ Disokong déning %1$s
+
+ Dumké liwat
+
+ Busak riwayat dlajahan
+
+ Bukak
+
+ Busak lan Bukak
+
+ Busak
+
+ Busak riwayat dlajahan
+
+
+ Busak & bukak
+
+ Ngenani %1$s, pitulung
+
+ Kontèn Wèb
+
+ Ganti aplikasi
+
+ Umum
+
+ Golèk
+
+ Njaluk saran panggolekan
+
+ Mesin panggolèk
+
+ Uripké
+
+ Paténi
+
+ Pilihan Data
+
+ Sinau liyané
+
+ Témpél utowo leboké URL
+
+ Busak
+
+ Busak
+
+ Basa
+ Gawan sistem
+
+ Privasi
+ Blokir kuki
+
+ Blokir JavaScript
+
+ Mozilla
+
+ Ngenani %1$s
+
+ Mesin panggolèk sing wis dipasang
+
+ Mulihaké mesin panggolèk standar
+
+ +Tambah mesin panggolèk liyané
+ Busak mesin panggolèk
+ Busak
+
+ Tambah mesin panggolèk
+
+ Jeneng mesin panggolèk
+ Simpen
+
+ Singkirké
+
+ Ngamot
+
+
+
+
+
+
+ Oké, mudheng!
+
+ -
+
+ Mbatalake
+
+ Undhuh Firefox
+
+ Jeneng Panganggo
+ Tembung wadi
+ Busak
+
+ Sambungan aman
+ Sambungan ora aman
+
+ URL wis ana
+
+ Golèki ing kaca
+
+ Golekì ing kaca
+
+ %1$d / %2$d
+ %1$d saka %2$d
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ka/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000000..ad8f3bd98a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ka/strings.xml
@@ -0,0 +1,1116 @@
+
+
+
+
+
+
+
+
+ გაუქმება
+
+ კარგი
+
+ შენახვა
+
+
+ მოძებნეთ ან შეიყვანეთ მისამართი
+
+ მხოლოდ თვალიერების პირადი რეჟიმი.\nმონახულება. წაშლა. თავიდან დაწყება.
+
+
+ მონახულებული გვერდების ისტორია წაიშალა.
+ დათვალიერების ისტორია გასუფთავდა
+
+
+ ჩანართებში მონახულებული გვერდების ისტორია გასუფთავებულია.
+
+
+ მოძებნა: %1$s
+
+
+ გვერდის გაზიარება…
+
+
+ საიტის ხარვეზის მოხსენება
+
+
+ %1$s-ში გახსნა
+
+
+ სხვაგან გახსნა…
+
+
+ მთავარ ეკრანზე დამატება
+
+
+ მალსახმობებში დამატება
+
+ მოცილება მალსახმობებიდან
+
+
+ პარამეტრები
+ შესახებ
+ დახმარება
+ თქვენი უფლებები
+
+
+ შეზღუდული მეთვალყურეები
+
+
+ გამორთვის შედეგად, საიტის გარკვეული ხარვეზები შეიძლება გამოსწორდეს
+
+
+ შიგთავსის შეზღუდვა
+
+ გამორთვა, საიტებზე ხარვეზების გამოსასწორებლად
+
+
+ უზრუნველყოფს %1$s
+
+
+ გაზიარება
+
+
+ ისტორიის წაშლა
+
+
+ გახსნა
+
+
+ გასუფთავება და გახსნა
+
+
+ გასუფთავება
+
+
+ ისტორიის გასუფთავება
+
+
+
+ გასუფთავება და გახსნა
+
+
+ გასუფთავდება და გაიხსნება %1$s
+
+
+
+ მოიძიეთ Focus-ით
+
+ მოიძიეთ Klar-ით
+
+ მოიძიეთ Focus Beta-თი
+
+ მოიძიეთ Focus Nightly-თ
+
+
+ %1$s სრულად გიბრუნებთ მართვის სადავეებს.
+გამოიყენეთ პირად ბრაუზერად:
+
+ მოძებნეთ და დაათვალიერეთ გვერდები პროგრამაშივე
+ შეზღუდეთ მეთვალყურეები (პარამეტრებიდან დაშვებაც შეიძლება)
+ გაასუფთავეთ და წაშალეთ ფუნთუშები, ძიებისა და თვალიერების ისტორია
+
+
+%1$s შექმნილია Mozilla-ს მიერ. ჩვენი მიზანი, ჯანსაღ და ღია ინტერნეტზე ზრუნვაა.
+ვრცლად
]]>
+
+
+ პირადულობა და უსაფრთხოება
+
+
+ თვალთვალი, ფუნთუშები, მონაცემთა შერჩევა
+
+
+ ნაგულისხმევად დაყენება, თვითშევსება
+
+
+
+
+ %1$s-ის შესახებ, დახმარება
+
+
+ თვალთვალისგან გაძლიერებული დაცვა
+
+
+ ვებშიგთავსი
+
+
+ პროგრამების გადართვისას
+
+
+ მთავარი
+
+
+ ნაგულისხმევი ბრაუზერი, ენა
+
+
+ მონაცემთა აღრიცხვა და გამოყენება
+
+ ძიება
+
+
+ ძიების შემოთავაზების ჩვენება
+
+ %1$s გაუგზავნის საძიებო სისტემას, მისამართების ველში აკრეფილ ტექსტს
+
+
+ ნაგულისხმევი
+
+
+ საძიებო სისტემა
+
+
+ ჩართული
+
+
+ გამორთული
+
+
+ ბმულის თვითდასრულება
+
+
+ რჩეული საიტებისთვის
+
+
+ ჩართეთ და %s თავად დაასრულებს 450-ზე მეტ ცნობილ URL-ბმულს, მისამართების ველში აკრეფისას.
+
+
+ თქვენ მიერ დამატებული საიტებისთვის
+
+
+ ჩართეთ და %s თავად დაასრულებს თქვენს რჩეულ URL-ბმულებს.
+
+
+ საიტების მართვა
+
+
+ საიტების მართვა
+
+
+ + საკუთარი URL-ების დამატება
+
+
+ თვითდასრულების თქვენი სია:
+
+
+ ბმულის დამატება
+
+
+ საკუთარი URL-ს დამატება
+
+
+ საკუთარი URL-ს დამატება
+
+
+ ბმულის დამატება თვითდასრულებისთვის
+
+
+ ფუნთუშები და საიტის მონაცემები
+
+
+ მონაცემთა შერჩევა
+
+
+ დამატებული URL-ების მოცილება
+
+
+ ვრცლად
+
+
+ დაამატეთ და მართეთ საკუთარი URL-ები თვითშევსებისთვის.
+
+
+ URL დასამატებლად
+
+
+ ჩასვით ან შეიყვანეთ URL
+
+
+ მაგალითად: mozilla.org
+
+
+ მაგალითად: example.com
+
+
+ ახალი URL დაემატა.
+
+
+ წაშლა
+
+
+ წაშლა
+
+
+ გადაამოწმეთ შეყვანილი URL.
+
+ ენა
+
+ სისტემის ნაგულისხმევი
+
+ პირადულობა
+ სარეკლამო მეთვალყურეების შეზღუდვა
+ ზოგიერთი სარეკლამო ელემენტი, თვალს ადევნებს თქვენს გადაადგილებას საიტებზე, მაშინაც კი როცა მათზე არ აწკაპებთ
+ სტატისტიკური აღრიცხვის ელემენტების შეზღუდვა
+ გამოიყენება მონაცემთა აღრიცხვისთვის, დამუშავებისა და ისეთი მოქმედებების შესამოწმებლად, როგორიცაა თითის შეხება და გვერდის გადაადგილება
+ სოცქსელების მეთვალყურეების შეზღუდვა
+ თქვენს გადაადგილებაზე თვალყურის მისადევნებლად, საიტებზე განთავსებულია მოწონების ან გაზიარების ღილაკების სახით
+ სხვა მეთვალყურე ელემენტების შეზღუდვა
+ ჩართვის შედეგად, ზოგიერთმა ვებგვერდმა, შესაძლოა გამართულად ვერ იმუშაოს
+ ფუნთუშების შეზღუდვა
+
+
+ არა, გმადლობთ
+ მხოლოდ გარეშე მეთვალყურეების
+ მხოლოდ გარეშე ფუნთუშების
+
+ საიტთაშორისი ფუნთუშების
+ დიახ, თუ შეიძლება
+
+
+ გამოიყენეთ თითის ანაბეჭდი, პროგრამის გასახსნელად
+
+
+ გახსენით თითის ანაბეჭდით, თუ დამატებული გაქვთ მალსახმობებში ან საიტისთვის უკვე გამოიყენეთ %s.
+
+
+ ფარული რეჟიმი
+
+ ვებგვერდების დამალვა პროგრამების გადართვისას და ეკრანის სურათების გადაღების შეზღუდვა.
+
+ უსაფრთხოება
+
+ წარმადობა
+ საიტის შრიფტების შეზღუდვა
+
+ შესაძლოა ხატულების და სურათების ნაწილი გაქრეს
+
+ JavaScript-ის შეზღუდვა
+
+ გვერდები, შესაძლოა უფრო სწრაფად ჩაიტვირთოს, მაგრამ ხარვეზებით იმუშაოს
+
+
+ %1$s-ის ნაგულისხმევ ბრაუზერად მითითება
+
+ Mozilla
+ მონაცემების გაგზავნა
+
+
+ იხილეთ ვრცლად
+
+
+ Mozilla აღრიცხავს მხოლოდ იმ მონაცემებს, რაც საჭიროა %1$s-ის გასაუმჯობესებლად.
+
+
+ პირადი მონაცემების დაცვა
+
+
+ ლიცენზიის შესახებ
+
+
+ ბიბლიოთეკები, რომელთაც ვიყენებთ
+
+
+ %s | OSS-ბიბლიოთეკები
+
+
+ %1$s-ის შესახებ
+
+
+ დაყენებული საძიებოები
+
+
+ საძიებო სისტემის არჩევა
+
+
+ აღდგეს ნაგულისხმევი საძიებოები
+
+
+ + სხვა საძიებო სისტემის დამატება
+ საძიებოს ამოშლა
+ მოცილება
+
+
+ სხვა საძიებო სისტემის დამატება
+
+ აირჩიეთ სასურველი საძიებო:
+
+
+ საძიებოს დამატება
+
+ საძიებოს დასახელება
+ საძიებო ფრაზა
+ შენახვა
+
+
+ მაგალითად: example.com/search/?q=%s
+
+ ახალი საძიებო სისტემა დამატებულია.
+
+ შეიყვანეთ საძიებო სისტემის სახელი
+ ამ სახელს, უკვე იყენებს ერთ-ერთი დაყენებული საძიებო სისტემა.
+
+ შეიყვანეთ საძიებო ფრაზა
+
+ შეამოწმეთ, შეესაბამება თუ არა საძიებო ფრაზა მოცემულ ფორმატს
+
+
+ შეტანილის გასუფთავება
+
+
+ დახურვა
+
+
+ ისტორიის წაშლა
+
+
+ გახსნილი ჩანართი: %1$s
+
+
+ დაცული კავშირი
+
+
+ იტვირთება
+
+
+ საიტი ჩაიტვირთა
+
+
+ დამატებითი პარამეტრები
+
+
+ დამატებითი პარამეტრების ღილაკი
+
+
+ გადასვლა წინ
+
+
+ საიტის ხელახლა ჩატვირთვა
+
+
+ გადასვლა უკან
+
+
+ საიტის ჩატვირთვის შეჩერება
+
+
+ წინა პროგრამაზე დაბრუნება
+
+
+ შეზღუდული მეთვალყურეები
+
+
+ მეთვალყურე ელემენტების შეზღუდვა
+
+ თქვენი უფლებები
+
+ ბმულის სხვა პროგრამით გახსნა
+
+ შეგიძლიათ დატოვოთ %1$s ბმულის %2$s-ში გასახსნელად.
+
+ პროგრამის მოძიება, რომელიც გახსნის ბმულს
+
+ თქვენს მოწყობილობაზე არსებულ არცერთ პროგრამას არ შეუძლია ამ ბმულის გახსნა. შეგიძლიათ დატოვოთ %1$s საჭირო პროგრამის %2$s-ში მოსაძიებლად.
+
+ გსურთ პირადი დათვალიერებიდან გასვლა?
+
+
+ %1$s დასრულდა
+
+
+ გახსნა
+
+
+
+
+
+
+
+
+
+
+ მალსახმობებში დაემატა!
+
+ გვერდი ვერ მოიძებნა
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ დახურვა
+
+
+
+ მოგესალმებათ %1$s
+
+
+ სწრაფი. პირადი. მხოლოდ საქმისკენ მიმართული.
+
+
+ დაიწყეთ
+
+
+
+ %1$s არ ჰგავს სხვა ბრაუზერებს
+
+
+ ისტორია გასუფთავდება პროგრამის დახურვისთანავე პირადულობის უკეთ დასაცავად.
+
+
+
+ გახადეთ %1$s ნაგულისხმევი, რომ დაიცვათ მონაცემები ყოველი ბმულის გახსნისას.
+
+
+ ნაგულისხმევ ბრაუზერად დაყენება
+
+
+ გამოტოვება
+
+
+
+ გააძლიერეთ პირადი მონაცემების დაცვა
+
+ პირადი მონაცემების უსაფრთხოება ახალ სიმაღლეზეა აყვანილი. შეზღუდეთ სარეკლამო და სხვა შიგთავსი, რომლითაც საიტებზე თქვენი დევნაა შესაძლებელი და შეამცირეთ გვერდების ჩატვირთვის დრო.
+
+
+ მოიძიეთ გვერდები, თქვენებურად
+
+ განსხვავებულად გსურთ მოიძიოთ? აირჩიეთ სასურველი საძიებო, პარამეტრებიდან.
+
+
+ მალსახმობების დამატება მთავარ ეკრანზე
+
+ %1$s-ის დახმარებით, სწრაფად შეგიძლიათ დაუბრუნდეთ რჩეულ საიტებს. მარტივად მიუთითეთ „მთავარ ეკრანზე დამატება“, %1$s-ის მენიუდან.
+
+
+ ჩვევად აქციეთ პირადულობის დაცვა
+
+ დააყენეთ %1$s ნაგულისხმევ ბრაუზერად და ისარგებლეთ პირადულობის დაცვით, სხვა პროგრამებიდან ბმულებზე გადასვლის შემთხვევაშიც.
+
+ კარგი, გასაგებია!
+ გამოტოვება
+ შემდეგი
+
+
+ -
+
+
+ დამატება
+
+
+ ᲓᲘᲐᲮ
+
+
+ გაუქმება
+
+
+ ᲐᲠᲐ
+
+
+ ამ მალსახმობით გაიხსნება, თვალთვალისგან გაძლიერებული დაცვის გარეშე
+
+
+ პირადი დათვალიერების რეჟიმი
+
+
+ შეტყობინებები საშუალებას გაძლევთ, ერთი შეხებით გაასუფთავოთ მონაცემები %1$s-ში. პროგრამის გახსნა, აღარ დაგჭირდებათ.
+
+
+ ისტორიის გასუფთავება
+
+
+ ჩამოტვირთეთ Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License და სხვა ღია წყაროს ლიცენზიებით.]]>
+
+
+ აქ.]]>
+
+
+ ლიცენზიებით.]]>
+
+
+ GNU General Public License v3 ლიცენზიით და ხელმისაწვდომია ამ ბმულზე .]]>
+
+
+ სახელი
+ პაროლი
+ გასუფთავება
+
+
+
+ დაცული კავშირი
+ დაუცველი კავშირი
+
+ დამმოწმებელი: %1$s
+
+
+ საიტის უსაფრთხოება
+ URL უკვე არსებობს
+
+
+ პოვნა გვერდზე
+
+
+ პოვნა გვერდზე
+
+
+ %1$d/%2$d
+
+ %1$d, %2$d-დან
+
+
+ მომდევნო შედეგი
+
+ წინა შედეგი
+
+ ძიების გაუქმება
+
+
+
+
+ სრული საიტის მოთხოვნა
+
+
+ სრული საიტი
+
+
+ ბმული აღებულია
+
+
+ შემმუშავებლის ხელსაწყოები
+
+
+ ბმულების გახსნა პროგრამებში
+
+
+ დამატებითი
+
+
+ საიტის ნებართვები
+
+
+ ფუნთუშის მოთხოვნების შემცირება
+
+
+ ჩართ.
+
+
+ გამორთ.
+
+
+ ფუნთუშის მოთხოვნების შემცირება
+
+
+ ნაკლებად იხილეთ ამომხტარი აბრები ფუნთუშის მოთხოვნების ავტომატურად უარყოფით, როცა კი შესაძლებელი იქნება.
+
+ -->
+ ფუნთუშის მოთხოვნების შემცირება
+
+
+ ჩართ. ამ საიტზე
+
+
+ საიტი ჯერ არაა მხარდაჭერილი
+
+
+ გამორთ. ამ საიტზე
+
+
+ ფუნთუშის მოთხოვნების შემცირება
+
+
+ გამორთ. ამ საიტზე
+
+
+ ჩართ. ამ საიტზე
+
+
+ ჩაირთოს ფუნთუშის მოთხოვნების შემცირება საიტზე %1$s?
+
+
+ გამოირთოს ფუნთუშის მოთხოვნების შემცირება საიტზე %1$s?
+
+
+ %1$s გაასუფთავებს ამ საიტის ფუნთუშებს და განაახლებს გვერდს. ყველა ფუნთუშის წაშლით შეიძლება გამოხვიდეთ ანგარიშებიდან და დაცარიელდეს საყიდლების კალათა.
+
+
+ %1$s ეცდება თავადვე უარყოს ფუნთუშების მოთხოვნები.
+
+
+ საიტზე ამჟამად არაა მხარდაჭერილი ფუნთუშების ნებართვების არიდება. გსურთ სთხოვოთ ჩვენს გუნდს, გადახედოს ამ ვებსაიტს და გახადოს მომავალში მხარდაჭერილი?
+
+
+ გაუქმება
+
+
+ მხარდაჭერის მოთხოვნა
+
+
+ მხარდაჭერის მოთხოვნა საიტისთვის გადაგზავნილია.
+
+
+ მხარდაჭერის მოთხოვნა საიტისთვის გადაგზავნილია.
+
+
+
+ %1$s ცდილობს უარყოს ფუნთუშების მოთხოვნები მომაბეზრებელი აბრების მოსაცილებლად.\n\nფუნთუშების აბრების სამართავად იხილეთ %2$s.
+
+ პარამეტრები
+
+
+ თვითგაშვება
+
+
+ დასაშვებად:
+
+
+ 1. გადადით Android-პარამეტრებში
+
+
+ ნებართვებს]]>
+
+
+ პარამეტრებში გადასვლა
+
+
+ %1$s ჩართულზე]]>
+
+
+ კამერა
+
+
+ მიკროფონი
+
+
+ მდებარეობა
+
+
+ შეტყობინება
+
+
+ DRM-დაქვემდებარებული შიგთავსი
+
+
+ კითხვა ყოველ ჯერზე
+
+
+ შეზღუდულია
+
+
+ დაშვებულია
+
+
+ ზღუდავს Android
+
+
+ ხმისა და ვიდეოს დაშვება
+
+
+ მხოლოდ ხმის შეზღუდვა
+
+
+ სასურველი
+
+
+ ხმისა და ვიდეოს შეზღუდვა
+
+
+ კვლევები
+
+
+ Firefox შეძლებს, დროდადრო კვლევების ჩატარებას.
+
+
+ ვრცლად
+
+
+ პროგრამა დაიხურება ცვლილებების ასახვისთვის
+
+
+ მოცილება
+
+
+ მოქმედი
+
+
+ დასრულებული
+
+
+ დაშორებული გამართვა USB/WiFi-ით
+
+
+ გახსნა
+
+
+ დაადასტურეთ თითის ანაბეჭდით
+
+
+ შეგიძლიათ, თითის ანაბეჭდის დამოწმებით განაგრძოთ გამოყენება.
+
+
+ ბმულის გახსნა სუფთა ჩანართით
+
+
+ თითის ანაბეჭდის ხატულა
+
+
+ ანაბეჭდის ამოცნობა ვერ მოხერხდა. სცადეთ ხელახლა.
+
+
+ თითი სწრაფად ამოძრავდა. სცადეთ ხელახლა.
+
+
+ გამოჩნდეს ძიების შემოთავაზებები?
+
+
+ შემოთავაზებების მისაღებად, %1$s საჭიროებს მისამართების ველში აკრეფილი ტექსტის, შერჩეული საძიებოსთვის გადაგზავნას.
+
+
+ არა
+
+
+ დიახ
+
+
+ ზოგიერთ საძიებო სისტემას, არ აქვს შემოთავაზებების ჩვენების შესაძლებლობა.
+
+
+ დახურვა
+
+
+
+
+ საიტი ხარვეზებით მუშაობს?\n სცადეთ თვალთვალისგან დაცვის გამორთვა
+
+
+ მთავარ ეკრანზე დამატება]]>
+
+
+ გახსნეთი ყველა ბმული %1$s-ით\n დააყენეთ %1$s ნაგულისხმევ ბრაუზერად
+
+
+ URL-ბმულების თვითშევსება სასურველი საიტებისთვის\n ნებისმიერ URL-ბმულზე ხანგრძლივი დაჭერით მისამართების ველში
+
+
+ გახსენით ბმული ახალ ჩანართში\n გვერდის ნებისმიერ ბმულზე ხანგრძლივი დაჭერით
+
+
+ რჩევების ჩვენების შეწყვეტა საწყის ეკრანზე
+
+
+ ახალი ჩანართი გაიხსნა
+
+
+ გადართვა
+
+
+ სრულ ეკრანზე გაშლა
+
+
+ ბმულის გახსნისას ახალ ჩანართში მაშინვე მასზე გადასვლა
+
+
+ შესაძლო საფრთხის მქონე და თაღლითური საიტების შეზღუდვა
+
+ თაღლითურ და მოიერიშედ მიჩნეული, აგრეთვე საზიანო და არასასურველი პროგრამების გამავრცელებელი საიტების შეზღუდვა.
+
+
+ მხოლოდ-HTTPS-რეჟიმი
+
+
+ თავადვე შეეცდება დაუკავშირდეს საიტებს დაშიფრული HTTPS-ოქმით მეტი უსაფრთხოებისთვის.
+
+
+ გამონაკლისები
+
+ ამ საიტებზე შიგთავსის შეზღუდვა გამორთული გაქვთ.
+
+ მოცილება
+
+ ყველა საიტის მოცილება
+
+
+ ფუნთუშების შეზღუდვა
+
+
+ გსურთ ფუნთუშების შეზღუდვა?
+
+
+ ჩანართი გაითიშა
+
+ ვწუხვართ. ამ ჩანართს რაღაც ხარვეზები ჰქონდა.
+
+ როგორც პირადი ბრაუზერი, ჩვენ არაფერს ვინახავთ და შესაბამისად არ შეგვიძლია ამ ჩანართის აღდგენა.
+
+ ჩანართის დახურვა
+
+
+
+
+
+ გათიშვის მოხსენების გადაგზავნა Mozilla-სთვის
+
+
+
+
+ შეზღუდული მეთვალყურე თარიღიდან %s
+
+ შიგთავსის
+
+ Სარეკლამო
+
+ სოცქსელების
+
+ საკვლევი
+
+ თვალთვალისგან გაძლიერებული დაცვა
+
+ დაცვა გამორთულია ამ საიტზე
+
+ დაცვა ჩართულია ამ საიტზე
+
+ კავშირი დაცულია
+
+ კავშირი დაუცველია
+
+ შესაზღუდი მეთვალყურეები
+
+
+ უკან
+
+
+
+ მოცილება
+
+
+ გადარქმევა
+
+ გადარქმევა
+
+
+ მალსახმობის სახელი
+
+
+ შენახული და გაზიარებული სურათები <b>არ წაიშლება</b>, როცა %1$s გასუფთავდება
+
+
+
+ იერსახე
+
+ ნათელი
+
+ მუქი
+
+ ბატარეის დამზოგის მიხედვით
+
+ მოწყობილობის იერსახის მიხედვით
+
+
+
+ ამ საიტზე მხარდაუჭერელია HTTPS
+
+
+ ვრცლად
+ შესაცვლელად იხილეთ პარამეტრები > პირადულობა და უსაფრთხოება > უსაფრთხოება.]]>
+
+
+ დაუცველი კავშირი
+
+
+
+ თუ აღნიშნულ საიტს მანამდე წარმატებით უკავშირდებოდით, ხარვეზი შეიძლება დროებითი იყოს.
+ ]]>
+
+
+ შესაძლოა, ვიღაც ამ საიტის სხვა საიტად გასაღებას ცდილობდეს და ჯობია, აღარ განაგრძოთ.
+
+ %1$s არ ენდობა %2$s -ს, რადგან მისი უსაფრთხოების სერტიფიკატის გამომცემი უცნობია, შეიძლება თავადვე აქვთ ხელმოწერილი ან სერვერი სათანადოდ არ აგზავნის შუალედურ სერტიფიკატებს.
+ ]]>
+
+
+
+ ჩანართის დახურვა
+
+
+
+ გამოჭერილია! ამ საიტს არ მივეცით თქვენი თვალთვალის საშუალება. შეეხეთ ფარის ნიშანს ნებისმიერ დროს და იხილეთ, რა იზღუდება.
+
+
+ ამომხტარი ფანჯრის დახურვა
+
+
+
+ დაცული ხართ!
+
+ ეს ნაგულისხმევი პარამეტრები ძლიერ დაცვას გთავაზობთ. თუმცა მარტივად შეგიძლიათ პარამეტრების შესწორება და საკუთარ საჭიროებებზე მორგება.
+
+ დახურვა
+
+
+ შეეხეთ აქ, რომ წაშალოთ სრულად — ისტორია, ფუნთუშები, ყველაფერი — და გახსნათ ახალი ჩანართი სუფთა ფურცლიდან.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ დახურვა
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ ვიჯეტის ძიება
+
+ !-- This is the title of promote search widget dialog. -->
+
+ დათვალიერების ისტორია გასუფთავებულია! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ წამოიწყეთ პირადი თვალიერება და იმთავითვე შევზღუდავთ მეთვალყურეებსა და სხვა მავნებლებს.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ახლა გადადით პირად ფანჯარაზე, თუმცა შემდეგ ჯერზე სწრაფად გახსნისთვის გამოიყენეთ %1$s-ვიჯეტი მთავარი ეკრანიდან.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ ვიჯეტის დამატება მთავარ ეკრანზე
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ ვიჯეტი დამატებულია მთავარ ეკრანზე
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-kaa/strings.xml b/mobile/android/focus-android/app/src/main/res/values-kaa/strings.xml
new file mode 100644
index 0000000000..41f2c2a64f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-kaa/strings.xml
@@ -0,0 +1,795 @@
+
+
+
+
+
+
+
+
+ Biykarlaw
+
+ MAQUL
+
+ Saqlaw
+
+
+ Mánzildi izleń yamasa kirgiziń
+
+
+ Siziń saytlardı kóriw tariyxıńız óshirildi.
+ Kóriw tariyxi tazalandı
+
+
+ Bettıń kóriw tariyxı óshirildi.
+
+
+ %1$s izleń
+
+
+ Bólisiw…
+
+
+ Sayt nasazlıǵı haqqında xabar beriw
+
+
+ %1$s de ashıw
+
+
+ …da ashıw
+
+
+ Tiykarǵı ekranǵa qosıw
+
+
+ Operativ buyrıqlarǵa qosıw
+
+
+ Operativ buyrıqlardan alıp tastaw
+
+ Sazlawlar
+ Baǵdarlama haqqında
+ Járdem
+
+ Siziń huqıqlarıńız
+
+
+ Baqlawshılar bloklandı
+
+
+ %1$s tárepinen islep shıǵıldı
+
+
+ Arqalı bólisiw
+
+
+ Kóriw tariyxin óshiriw
+
+
+ Ashıw
+
+
+ Óshiriw hám ashıw
+
+
+ Óshiriw
+
+
+
+ Kóriw tariyxin óshiriw
+
+
+
+ Óshiriw hám ashıw
+
+
+ Focusda izlew
+
+ Klarda izlew
+
+ Focus Betada izlew
+
+ Focus Nightlyda izlew
+
+
+ Qupıyalıq hám Qorǵanıw
+
+
+
+
+ %1$s haqqında, járdem
+
+
+ Baqlawdan keńeytilgen qorǵanıw
+
+
+ Baǵdarlamalar almasıwı
+
+
+ Ulıwmalıq
+
+
+ Aldınnan ornatılǵan brauzer, til
+
+
+ Maǵlıwmatlardı jıynaw hám qollanıw
+
+ Izlew
+
+
+ Izlew usınısların alıw
+
+
+ %s mánzil qatarına kirgizgen tekstlerińizdi izlew sistemasına jiberedi
+
+
+ Aldınnan ornatılǵan
+
+
+ Izlew sisteması
+
+
+ Qosıwlı
+
+
+ Óshirilgen
+
+
+ Siz qosqan saytlar ushın
+
+
+ Saytlardı basqarıw
+
+
+ Saytlardı basqarıw
+
+
+ + Jeke URL qosıw
+
+
+ URL qosıw
+
+
+ Jeke URL qosıw
+
+
+ Jeke URL qosıw
+
+
+ Cookieler hám sayt maǵlıwmatları
+
+
+ Jeke URLdı óshiriw
+
+
+ Tolıǵıraq úyreniw
+
+
+ Qosıw ushın URL
+
+
+ URL mánzildi kiritiń yamasa qoyıń
+
+
+ Mısal: mozilla.org
+
+
+ Mısal: example.com
+
+
+ Jańa jeke URL qosıldı.
+
+
+ Alıp taslaw
+
+
+ Alıp taslaw
+
+
+ Kiritilgen URLdı dıqqat penen tekseriń.
+
+ Til
+
+
+ Qurılma tili
+
+ Qupıyalıq
+
+ Reklama baqlawların bloklaw
+
+ Analitika baqlawshıların bloklaw
+ Social baqlawlardı bloklaw
+ Basqa kontent baqlawshıların bloklaw
+ Cookielardı bloklaw
+
+
+ Yaq, raxmet
+ Saytlar aralıq cookielardı bloklaw
+ Awa, ótinish
+
+
+ Baǵdarlamanı bloktan shıǵarıw ushın barmaq izin qollanıń
+
+
+ Kórinbes
+
+ Baǵdarlamalardı almastırǵanda veb-betlerdi jasırıw hám skrinshot alıwdı bloklaw.
+
+ Qorǵanıw
+
+ Ónimdarlıq
+ Veb-shriftlardı bloklaw
+
+ JavaScriptti bloklaw
+
+
+ %1$stı tiykarǵi brauzer retinde ornatıw
+
+ Mozilla
+
+ Paydalanıw haqqındaǵı maǵlıwmatlardı jiberiw
+
+
+ Tolıǵıraq úyreniw
+
+
+ Qupıyalıq esletpeleri
+
+
+ %1$s haqqında
+
+
+ Ornatılǵan izlew sistemaları
+
+
+ Izlew sistemasın tańlań
+
+
+ Aldınnan ornatılǵan izlew sistemaların qayta ornatıw
+
+
+ + Basqa izlew sistemasın qosıw
+ Izlew sistemaların alıp taslaw
+ Alıp taslaw
+
+
+ Basqa izlew sistemasın qosıw
+
+ Ózińizdiń izlew sistemańizdi tańlań:
+
+
+ Izlew sistemasın qosıw
+
+ Izlew sistemasınıń ataması
+ Paydalanıw ushın izlew qatarı
+ Saqlaw
+
+
+ Mısal: example.com/search/?q=%s
+
+ Jańa izlew sisteması qosıldı.
+
+ Izlew sistemasınıń atamasın kirgiziń
+
+ Izlew qatarın kirgiziń
+
+ Izlew qatarınıń úlgi formatına sáykes keliwin tekseriń
+
+
+ Ótkerip jiberiw
+
+
+ Kóriw tariyxin óshiriw
+
+
+ Ashılǵan betler: %1$s
+
+
+ Qáwipsiz jalǵanıw
+
+
+ Júklenbekte
+
+
+ Sayt júklendi
+
+
+ Basqa variantlar
+
+
+ Qosımsha sazlawlar túymesi
+
+
+ Aldıǵa
+
+
+ Sayttı qayta júklew
+
+
+ Artqa qaytıw
+
+
+ Sayttı júklewdi toqtatıw
+
+
+ Aldınǵı baǵdarlamaǵa qaytıw
+
+
+ Bloklanǵan baqlawshılar
+
+
+ Baqlawshılardı bloklaw
+
+ Siziń huqıqlarıńız
+
+ Siltemeni basqa baǵdarlamada ashıw
+
+ Bul siltemeni %2$sda ashıw ushın, siz %1$s baǵdarlamasınan shıǵıwıńız múmkin.
+
+ Jeke kóriwden shıǵıwdı qáleysiz be?
+
+
+ %1$s júklendi
+
+
+ Ashıw
+
+ Server tabılmadı
+
+
+ Jabıw
+
+
+
+ %1$sǵa xosh kelibsiz
+
+
+ Baslaw
+
+
+
+ %1$s basqa brauzerlarǵa uqsamaydı
+
+
+ Tiykarǵi brauzer retinde ornatıw
+
+
+ Ótkerip jiberiw
+
+
+ Siziń izlew, siziń internetińiz
+
+
+ Tiykarǵı ekranǵa operativ buyrıqlardı qosıń
+
+
+ Qupıyalıqtı ádetke aylandırıń
+
+ MAQUL, túsinikli!
+ Ótkerip jiberiw
+
+ Keyingi
+
+
+ -
+
+
+ Qosıw
+
+ AWA
+
+
+ Biykarlaw
+
+ YAQ
+
+
+ Jeke kóriw seansı
+
+
+ Kóriw tariyxin óshiriw
+
+
+ Firefoxtı júklep alıw
+
+
+ Paydalanıwshı atı
+ Parol
+ Tazalaw
+
+
+
+ Qáwipsiz jalǵanıw
+
+ Qáwipsiz emes jalǵanıw
+
+ Tastıyıqlawshı: %1$s
+
+
+ Sayt qáwipsizligi
+ URL álleqashan bar
+
+
+ Betten tabıw
+
+
+ Betten tabıw
+
+
+ %1$d/%2$d
+
+ %1$d dan %2$d
+
+
+ Keyingi nátiyjeni tabıw
+
+ Aldınǵı nátiyjeni tabıw
+
+ Betten izlewdi alıp taslaw
+
+
+ Sayttıń tolıq versiyasın soraw
+
+
+ Sayttıń kompyuter versiyası
+
+
+ Silteme kóshirip alındı
+
+
+ Baǵdarlamashı ásbapları
+
+
+ Siltemelerdi baǵdarlamalarda ashıw
+
+
+ Qosımsha
+
+
+ Sayt ruqsatnamaları
+
+
+ Cookie haqqındaǵı xabarlamalardı qısqartıw
+
+
+ Qosıwlı
+
+
+ Óshirilgen
+
+
+ Cookie haqqındaǵı xabarlamalardı qısqartıw
+
+ -->
+ Cookie haqqındaǵı xabarlamalardı qısqartıw
+
+
+ Bul sayt ushın QOSILǴAN
+
+
+ Sayt házirshe qollap-quwatlanbaydı
+
+
+ Bul sayt ushın ÓSHIRILGEN
+
+
+ Cookie haqqındaǵı xabarlamalardı qısqartıw
+
+
+ Bul sayt ushın ÓSHIRILGEN
+
+
+ Bul sayt ushın QOSILǴAN
+
+
+ %1$s ushın cookie haqqındaǵı xabarlamalardı qısqartıwdı qosayıq pa?
+
+
+ %1$s ushın cookie haqqındaǵı xabarlamalardı qısqartıwdı óshireyik pe?
+
+
+ %1$s bul sayttıń cookieların óshiredi hám betti jańalaydı. Barlıq cookielardı óshiriw sizdi sistemadan shıǵarıwı yamasa sawda islew sebetińizdi bosatıwı múmkin.
+
+
+ %1$s kuki sorawların avtomatikalıq biykarlawǵa háreket qılıwı múmkin.
+
+
+ Biykarlaw
+
+
+ Qollap-quwatlawdı soraw
+
+
+ Qollap-quwatlaw haqqındaǵı soraw jiberildi.
+
+
+ Qollap-quwatlaw haqqındaǵı soraw jiberildi.
+
+
+ sazlawlar
+
+
+ Avtomatikalıq iske túsiriw
+
+
+ Ruqsat beriw ushın:
+
+
+ 1. Android sazlawların ashıń
+
+
+ Ruqsatlar ústine basıń]]>
+
+
+ Sazlawlarǵa ótiw
+
+
+ %1$s ruqsat berilgen jaǵdayına ótkeriń]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Jaylasıw ornı
+
+
+ Xabarlama
+
+
+ Avtor huqıqları menen qorǵalǵan kontent
+
+
+ Ruqsat beriwdi sorań
+
+
+ Bloklanǵan
+
+
+ Ruqsat etildi
+
+
+ Android tárepinen bloklanǵan
+
+
+ Audio hám videoǵa ruqsat beriw
+
+
+ Tek audionı bloklaw
+
+
+ Usınıs etilgen
+
+
+ Audio hám videonı bloklaw
+
+
+ Izertlewler
+
+
+ Firefox waqtı-waqtı menen izleniwlerdi ornatıwı hám júrgiziwi múmkin.
+
+
+ Tolıǵıraq úyreniw
+
+
+ Ózgerislerdi qollanıw ushın baǵdarlama jabıladı
+
+
+ Alıp taslaw
+
+
+ Aktiv
+
+
+ Aralıqtan USB/Wi-Fi arqalı nasazlıqtı sazlaw
+
+
+ Bloktan shıǵarıw
+
+
+ Barmaq izińiz járdeminde tastıyıqlań
+
+
+ Siltemeni jańa seansda ashıw
+
+
+ Barmaq izleri anıqlanbadı. Qayta urınıp kóriń.
+
+
+ Izlew usınısları kórsetilsin be?
+
+
+ Yaq
+
+
+ Awa
+
+
+ Aryım izlew sistemaları usınıslardı kórsete almaydı.
+
+
+ Jabıw
+
+
+
+
+ Jańa bet ashıldı
+
+
+ Ótiw
+
+
+ Tolıq ekran rejimi
+
+
+ Tek HTTPS rejimi
+
+
+ Qáwipsizlikti arttırıw ushın HTTPS shifrlaw protokolı járdeminde saytlarǵa avtomat tárizde jalǵanıwǵa háreket etedi.
+
+
+ Esaptan tısqarılar
+
+ Alıp taslaw
+
+
+ Barlıq veb-saytlardı óshiriw
+
+
+ Cookielardı bloklaw
+
+
+ Cookielardı bloklawdı qáleysiz be?
+
+
+ Keshiresiz. Bul bet penen mashqala payda boldı.
+
+ Betti jabıw
+
+
+ Nasazlıq esabatın Mozillaǵa jiberiw
+
+
+
+
+ Kontent
+
+ Reklama
+
+ Social tarmaqlar
+
+
+ Analitika
+
+ Baqlawdan keńeytilgen qorǵanıw
+
+ Bul sayt ushın qorǵanıw óshirilgen
+
+ Bul sayt ushın qorǵanıw qosıwlı
+
+ Jalǵanıw qáwipsiz
+
+ Jalǵanıw qáwipsiz emes
+
+
+ Artqa qaytıw
+
+
+
+ Alıp taslaw
+
+ Qayta ataw
+
+ Qayta ataw
+
+ Operativ buyrıq ataması
+
+
+
+ Tema
+
+ Jaqtı
+
+ Qarańǵı
+
+ Batareyanı únemlew rejimin názerde tutıw
+
+ Qurılma temasına ámel etiń
+
+
+ Bul sayt HTTPSdı qollap-quwatlamaydı
+
+
+ Jalǵanıw qáwipsiz emes
+
+
+
+ Betti jabıw
+
+
+ Jabıw
+
+
+
+
+ Jabıw
+
+
+ Izlew vidjeti
+
+
+ Kóriw tariyxi óshirildi 🎉
+
+
+ Vidjetti bas ekranǵa qosıw
+
+
+ Vidjet bas ekranǵa qosıldı
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-kab/strings.xml b/mobile/android/focus-android/app/src/main/res/values-kab/strings.xml
new file mode 100644
index 0000000000..43205cf53b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-kab/strings.xml
@@ -0,0 +1,1123 @@
+
+
+
+
+
+
+
+
+ Sefsex
+
+ IH
+
+ Sekles
+
+
+ Nadi neγ sekcem tansa
+
+ Tunigin tusligt tawurmant.\nSnirem. Kkes. Sniles.
+
+
+ Amazray-inek n unadi yettwasfeḍ.
+
+ Azray n tunigin yettwasfeḍ
+
+
+ Amazray n tunigin n yiccer yettwasfed.
+
+
+ Nadi %1$s
+
+
+ Bḍu…
+
+
+ Azen-d ugur-agi
+
+
+ Ldi di %1$s
+
+
+ Ldi deg…
+
+
+ Rnu γer ugdil agejdan
+
+
+ Rnu ɣer yinegzumen n unasiw
+
+ Kkes seg yinegzumen
+
+
+ Iγewwaṛen
+ Γef
+ Tallalt
+ Izerfan-inek
+
+
+ Ineḍfaṛen ttwaḥebsen
+
+
+ Ma tsenseḍ aya, izmer ad ttwasegmen kra n uguren
+
+
+ Asewḥel n ugbur
+
+ Sens akken ad tqaεḍeḍ kra n ismal
+
+
+ Ddaw leɛnaya n %1$s
+
+
+ Bdu s
+
+ Sfeḍ amazray n tunigin?
+
+ Sit ɣef telɣut-a neɣ kkes-itt i wakken ad tekkseḍ azray-ik n tunigin s wudem aɣelsan.
+
+
+ Sit ɣef telɣut-a neɣ zuɣer-itt i wakken ad tekkseḍ azray-ik n tunigin s wudem aɣelsan.
+
+ Sfeḍ amazray n tunigin
+
+
+ Ldi
+
+
+ Sfeḍ sakin Ldi
+
+
+ Sfeḍ
+
+
+ Sfeḍ amazray n tunigin
+
+
+
+ Sfeḍ & Ldi
+
+
+ Sfeḍ sakin ldi %1$s
+
+
+
+ Nadi deg Focus
+
+ Nadi deg Klar
+
+ Nadi deg Focus Beta
+
+ Nadi deg Focus Nightly
+
+
+ %1$s ad k-d-yefk afus ad teqqimeḍ d aqeṛṛu.
+Seqdec-it d iminig uslig:
+
+ Nadi sakin snirem akken iwata deg usnas
+ Sewḥel ineḍfaren (neɣ leqqem iɣewwaren akken ad tsirgeḍ ineḍfaren)
+ Sfeḍ inagan n tuqqna akked uzray n tunigin d unadi
+
+
+%1$s yesnulfa-t-id Mozilla. Tuɣdaṭ-nneɣ ad nḥareb γef internet yeldin d tezmert-is.
+Issin ugar
]]>
+
+
+ "Tabaḍnit & Taɣellist"
+
+
+ Aḍfaṛ, inagan n tuqqna, isefka yettwaleqḍen
+
+
+ Amsedday amezwer, tira tawurmant
+
+
+
+
+ Ɣef %1$s, tallelt
+
+
+ Ammesten mgal aḍfaṛ yettwaseǧhed
+
+
+ Agbur Web
+
+
+ Tunigin gar yisnasen
+
+
+ Amatu
+
+
+ Iminig amezwer, tutlayt
+
+
+ Alqaḍ & Aseqdec n yisefka
+
+ Nadi
+
+
+ Sken isumar n unadi
+
+ %1$s ad ak-n-yazen tawsit-inek deg ufeggag n tansiwin ɣer umsedday-inek n unadi
+
+
+ Amezwer
+
+
+ Amsedday n unadi
+
+
+ Yermed
+
+
+ Yensa
+
+
+ Taččart tawurmant URL
+
+
+ I yismal ifazen
+
+
+ %s ur yizmir ara ad yesεu taččart tawurmant i ugar n 450 n URLs deg ufeggag n tansiwin.
+
+
+ I yismal ad ternuḍ
+
+
+ Ur izmir ara ad isεu %s taččart tawurmant n URLs inek i tḥemmleḍ.
+
+
+ Sefrerk ismal
+
+
+ Sefrerk ismal
+
+
+ + Rnu URL aganan
+
+
+ Tabdart-ik·im n usmad awurman:
+
+
+ Rnu URL
+
+
+ Rnu URL aganan
+
+
+ Rnu URL aganan
+
+
+ Rnu aseɣwen i taččart tawurmant
+
+
+ Inagan n tuqqna d yisefka n yismal
+
+
+ Alqaḍ n yisefka
+
+
+ Kkes URLs iganen
+
+
+ Issin ugar
+
+
+ Rnu u sefrek taččart tawurmant n URLs.
+
+
+ URL ad ternuḍ
+
+
+ Senṭeḍ neγ sekcem URL
+
+
+ Amedya: mozilla.org
+
+
+ Amedya: example.com
+
+
+ URL aganan amaynut ittwarna.
+
+
+ Kkes
+
+
+ Kkes
+
+
+ Senned snat γef URL i tesriḍ.
+
+ Tutlayt
+
+ Amezwer n unagraw
+
+ Tabaḍnit
+ Sewḥel ineḍfaṛen
+ Kra n udellel iṭṭafaṛ tirza inek γer izmal web, Γas ur tsit ara γef udellel
+ Sewḥel ineḍfaṛen usliḍen
+ Ittwaseqdec i welqqaḍ , Aslaḍ d uktili n wamud am usekcem n isekillen akked usenned
+ Sewḥel ineḍfaṛen n tmetti
+ Ittwarna deg kra n ismal akken ad isfuγel tirziwin inek , akken daγen ad d-yesken timahaltin am tqefalt n beṭṭu
+ Sewḥel ineḍfaṛen-nniḍen n ugbur
+ Armad n uγwwaṛèagi izmer ad yeglu s kra n wuguren di kra n isebtar
+ Sewḥel inagan n tuqna
+
+
+ Uhu, tanemmirt
+ Sewḥel kan inagan n tuqqna n ufecku wis 3
+ Sewḥel kan inagan n tuqqna n ufecku wis 3
+
+ Sewḥel inagan n tuqqna gar yismal
+ Ih, tanemmirt
+
+
+ Seqdec adsil umḍin akken ad tserḥeḍ i usnas
+
+
+ Kkes asekkeṛ s useqdec n udsil umḍin ma yella terniḍ inegzumen n unasiw neɣ ma yili asmel web yeldi yakan deg %s.
+
+
+ Tusligt
+
+ Ffer isebtar web ticki tettbeddileḍ gar isnasen sakin sewḥel tuṭṭfa n ugdil.
+
+ Taɣellist
+
+ Tamellit
+ Sewḥel tisefsiyin Web
+
+ Ahat yekka-d seg tigitin neγ tugniwin yettwattun
+
+ Sewḥel JavaScript
+
+ Izmer ad d-yali usebter s wudem arurad, maca izmer daγen ad yili wugur
+
+
+ Err %1$s d iminig amezwer
+
+ Mozilla
+ Azen isefka n useqdec
+
+
+ Issin ugar
+
+
+ Mozilla tettennaɣ akken ad telqeḍ kan ayen tesra i usnulfu d usnernu n %1$s i yal uiwen.
+
+
+ Tasertit n tbaḍnit
+
+
+ Talɣut n turagt
+
+
+ Timkerḍiyin i nseqdac
+
+
+ %s | timkerḍiyin OSS
+
+
+ Γef %1$s
+
+
+ Imseddayen n unadi ibedden
+
+
+ Fren amsedday n unadi
+
+
+ Err-d imseddayen n unadi imezwar
+
+
+ + Rnu amsedday nniḍen n unadi
+ Kkes imseddayen n unadi
+ Kkes
+
+
+ Rnu amsedday n unadi wayeḍ
+
+
+ Fren amsedday-ine·inem n unadi ufrin
+
+
+ Rnu amsedday n unadi
+
+ Isem n umsedday n unadi
+ Nadi aḍris ad tesqedceḍ
+ Sekles
+
+
+ Amedya: example.com/search/?q=%s
+
+ Amesday n unadi amaynut ittwarna.
+
+ Sekcem isem n umsedday n unadi
+ Amsedday n unadi s yisem-a ittwasebded yakan.
+
+ Sekcem aḍris n unadi
+
+ Senqed akken aḍris n unadi yemsaḍa akked tawsit n umedya
+
+
+ Sfeḍ anekcam
+
+
+ Kkes
+
+
+ Kkes amazray n tunigin
+
+
+ Iccaren ldin: %1$s
+
+
+ Tuqqna taγelsant
+
+
+ Asali
+
+
+ Asmel web yuli-d
+
+
+ Ugar n iγewwaṛen
+
+
+ Tageffalt n vewwaṛen-nniḍen
+
+
+ Inig ar zdat
+
+
+ Ales asali n usmel web
+
+
+ Inig ar deffir
+
+
+ Seḥbes asali n usmel
+
+
+ Uγal ar usnas izrin
+
+
+ Amḍan n ineḍfaṛen ittwaḥebsen
+
+
+ Sewḥel ineḍfaṛen
+
+ Izerfan-ik
+
+ Ldi aseγwen deg usnas-nniḍen
+
+ Tzemred ad tefγeḍ seg %1$s akken ad d-ldiḍ aseγwen di %2$s.
+
+ Aff-d asnas izemren ad d-yeldi aseγwen
+
+ Ulac asnas deg ibenk-ik izemren ad d-yeldi aseγwen-agi. Tebγiḍ ad teffγeḍ si %1$s sakin ad d-nadiḍ %2$s akken ad d-tafeḍ asnas ilaqen.
+
+ Ffeγ si tunigin tusligt?
+
+
+ %1$s yefuk
+
+
+ Ldi
+
+
+
+
+
+
+
+
+
+
+ Yettwarna ɣer yinegzumen!
+
+ Ulac aqeddac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mdel
+
+
+
+ Welcome to %1$s
+
+
+
+ D arurad. D uslig. Ulac yesruḥuyen ddehn.
+
+
+ Bdu
+
+
+
+ %1$s mačči am yiminigen-nniḍen
+
+
+ Anseffeḍ azray-ik•im mi ara tmedleḍ asnas i wugar n lemqadra n tbaḍnit.
+
+
+
+ Err %1$s d iminig-ik·im amezwer i ummesten n yifeka-k·m yal tikkelt mi ara teldiḍ aseɣwen.
+
+
+ Sbadu-t d iminig amezwer
+
+
+ Suref
+
+
+
+ Rmed tabaḍnit inek
+
+ Iminig uslig ad yali deg uswir.Dayen ur tuγaleḍ ad twaliḍ adellel neγ imsfuγal i k-ṭṭafaren u saẓayen tulya n isemal web.
+
+
+ Anadi-ik, abrid-ik
+
+ Tettnadiḍ ayen-nniḍen? Fren amsadday-nniḍen n unadi amezwer deg iɣewwaṛen.
+
+
+ Rnu anegzum ar ugdil-ik agejdan
+
+ Uɣal ar yesmal-ik inurifen deg %1$s s zreb. Fren kan \"Rnu ar ugdil agejdan\" seg umuɣ %1$s.
+
+
+ Err tabaḍnit d tanumi
+
+ Sbadu %1$s d iminig-ik amezwar sakin faṛeṣ tagnit n tunigin tusligt ticki teldiḍ-d isebtar web seg isnasen-nniḍen.
+
+ IH, Awi-t-id!
+ Suref
+ Γer zdat
+
+
+ -
+
+
+ Rnu
+
+
+ IH
+
+
+ Sefsex
+
+
+ UHU
+
+
+ Anegzum-a ad d-yeldi s ummesten yettuḥettmen mgal aḍfaṛ
+
+
+ Tiɣimit n tunigin tusligt
+
+
+ Ilɣa ad k-ǧǧen ad tsefḍeḍ tiɣimit n %1$s-ik s unali. Ur tesrid ara ad teldiḍ asnas neɣ ad twaliḍ ayen yettwaselkamen deg iminig-ik.
+
+
+ Sfeḍ amazray n tunigin
+
+
+ Sader Firefox
+
+
+
+
+
+
+
+
+ Turagt tazayazt n Mozillaakked turagin-nniḍen n uɣbalu yeldin.]]>
+
+
+ dagi.]]>
+
+
+ turagin tiyaḍ yemgaraden d tilelliyin yerna n uɣbalu yeldin.]]>
+
+
+ Turagt tazayazt tamatut GNU v3, yernza tella dagi .]]>
+
+
+ Isem n useqdac
+ Awal uffir
+ Sfeḍ
+
+
+
+ Tuqqna taɣelsant
+ Tuqqna taraɣelsant
+
+ Isenqed-it: %1$s
+
+
+ Taɣellist n usmel web
+ URL Yella yakan
+
+
+ Af deg usebter
+
+
+ Nadi deg usebter
+
+
+ %1$d/%2$d
+
+ %1$d ɣef %2$d
+
+
+ Af-d agmuḍ d-iteddun
+
+ af-d agmuḍ yezrin
+
+ Mdel anadi deg usebter
+
+
+
+
+ Suter asmel n tnarit
+
+
+ Asmel n tnarit
+
+
+ Tansa URL tettwanγel
+
+
+ Ifecka n tneflit
+
+
+ Ldi iseɣwan deg yisnasen
+
+
+ Talqayt
+
+
+ Tisirag n usmel
+
+
+ Asenqes n yiɣarracen n yinagan n tuqqna
+
+
+ Yermed
+
+
+ Yensa
+
+
+ Asenqes n yiɣarracen n yinagan n tuqqna
+
+
+ Wali kra kan n yiɣarracen s ugdal awurman n yissutar n yinagan n tuqqna mi ara tettunefk tegnit.
+
+ -->
+ Asenqes n yiɣarracen n yinagan n tuqqna
+
+
+ Rmed i usmel-a
+
+
+ Asmel-a ur yettusefrak ara akka tura
+
+
+ Sens i usmel-a
+
+
+ Asenqes n yiɣarracen n yinagan n tuqqna
+
+
+ Sens i usmel-a
+
+
+ Rmed i usmel-a
+
+
+ Rmed asenqes n yiɣerracen n yinagan n tuqqna i %1$s?
+
+
+ Sens asenqes n yiɣerracen n yinagan n tuqqna i %1$s?
+
+
+ %1$s ad isfeḍ inagan n tuqqna n usmel-a syen ad issesfer asebter. Asfaḍ meṛṛa n yinagan n tuqqna yezmer ad ak·am-isseḥbes tuqqna neɣ ad yenɣel tiqecwalin n tiɣtin.
+
+
+ %1$s yezmer ad yeɛreḍ s wudem awurman ad yagi issutar n yinagan n tuqqna.
+
+
+ Asmel-a ur yettusefrak ara akka tura s usenqes n yiɣarracen n yinagan n tuqqna. Tebɣiḍ ad tessutreḍ tarbaɛt-nneɣ ad tessenqed asmel-a, ad ternu asefrek-ines ar sdat?
+
+
+ Sefsex
+
+
+ Ssuter tallalt
+
+
+ Assuter n usefrek n usmel yettwazen.
+
+
+ Assuter n usefrek n usmel yettwazen.
+
+
+
+ %1$s yettaɛraḍ ad yagi issutar n yinagan n tuqqna i wakken ad tekksem iɣarracen n yinagan n tuqqna.\n\nSefrek ismenyifen n yiɣarracen n yinagan n tuqqna deg %2$s.
+
+
+ iɣewwaren
+
+
+ Taɣuri tawurmant
+
+
+ Sireg-it:
+
+
+ 1. Ddu ɣer iɣewwaṛen Android
+
+
+ Tisirag]]>
+
+
+ Ddu ɣer iɣewwaṛen
+
+
+ %1$s ɣer IRMED]]>
+
+
+ Takamiṛat
+
+
+ Asawaḍ
+
+
+ Adig
+
+
+ Alɣu
+
+
+ Agbur yettwaḥerzen s DRM
+
+
+ Suter asireg
+
+
+ Iwḥel
+
+
+ Ittusireg
+
+
+ Iwḥel sɣur Android
+
+
+ Sireg ameslaw d uvidyu
+
+
+ Sewḥel kan imeslawen
+
+
+ Yelha
+
+
+ Sewḥel imeslawen d tvidyutin
+
+
+ Leqraya
+
+
+ Firefox yezmer ad isebded yerna ad iseddu leqraya seg wakud ɣer wayeḍ.
+
+
+ Issin ugar
+
+
+ Asnas ad yemdel i wakken ad ttusnasen ibiddilen
+
+
+ Kkes
+
+
+ Urmid
+
+
+ Yemmed
+
+
+ Taseɣtit tanmeggagt s USB/Wi-Fii
+
+
+ Serreḥ
+
+
+ Sentem aseqdec n udlis-ik umḍin
+
+
+ Tzemreḍ ad tesqedceḍ adsil-ik umḍin i wakken ad tkemmleḍ tiɣimit-ik n usnas tamirant.
+
+
+ Ldi aseɣwen deg tɣimit tamaynut
+
+
+ Tagnit n udsil umḍin
+
+
+ Ur yaêqil ara aḍaḍ. Ԑreḍ tikkelt-nniḍen.
+
+
+ TƐherkeḍ aḍaḍ s lweǧlan. Ԑreḍ tikelt-nniḍen.
+
+
+ Sen isumar n unadi?
+
+
+ Akken ad tawiḍ isumar n unadi, %1$s isra ad yazen ayen tettaruḍ deg ufeggag n tansa ɣer umsedday n unadi.
+
+
+ Ala
+
+
+ Ih
+
+
+ Kra n imseddayen n unadi ur zmiren ara ad d-sken isumar.
+
+
+ Zgel
+
+
+
+
+ Tigawin ur nettwerǧu sɣur usmel?\n Ɛṛeḍ ad tsenseḍ ammesten n usfuɣel
+
+
+ Rnu ɣer ugdil agejdan"]]>
+
+
+ Ldi yal aseɣwen deg %1$s\n Sbadu %1$s d iming amezwar
+
+
+ Taččarant tawurmant URLs i yismal i tseqdaceḍ aṭas \n Asenned ɣezzifen ɣef URL ibɣu yilli deg ufeggag n tansiwin
+
+
+ Ldi aseɣwen deg iccer amaynut\n Asenned ɣezzifen ɣef useɣwen deg usebter
+
+
+ Sens timahaltin deg ugdil n tnekra
+
+
+ Iccer amaynut yeldi
+
+
+ Nṭew
+
+
+ Askar n ugdil ačaran yermed
+
+
+ Nṭew ɣer useɣwen deg iccer amaynut
+
+
+ Sewḥel yir agbur neɣ ismal iweɛṛen
+
+ Inni-aɣ-d ɣef tigawin n usaxser n ismal, ismal n uḍfar, akked isaɣzan n ddiri deg ismal.
+
+
+ Askar HTTPS-Only
+
+
+ Ad yeɛreḍ ad iqqen ɣer ismal iseqdacen aprotokol n uwgelhen HTTPS i ugar n teɣelist.
+
+
+ Tisuraf
+
+ Tsenseḍ asewḥel n ugbur i yismal-agi.
+
+ Kkes
+
+ Kkes akk ismal web
+
+
+ Sewḥel inagan n tuqqna
+
+
+ Tebɣiḍ ad tesweḥleḍ inagan n tuqqna?
+
+
+ Yaɣli iccer
+
+ Nesḥasef. nesεa ugur akked iccer-agi.
+
+ Immi d iminig uslig, werǧin ad nsekles neɣ ad d-nner iccaren.
+
+ Mdel Iccer
+
+
+
+
+
+ Azen aneqqis n uɣelluy i Mozilla
+
+
+
+
+ Ineḍfaren ttusweḥlen seg %s
+
+ Agbur
+
+ Adellel
+
+ Anmetti
+
+ Tidaddanin
+
+ Ammesten mgal aḍfaṛ yettwaseǧhed
+
+ Ammesten yensa i usmel-a
+
+ Ammesten yermed i usmel-a
+
+ Tuqqna d taɣellsant
+
+ Tuqqna mačči d taɣellsant
+
+ Ineḍfaren d yisekripten ara yettusweḥlen
+
+
+ Uɣal ɣer deffir
+
+
+
+ Kkes
+
+
+ Snifel isem
+
+ Snifel isem
+
+
+ Isem n unegzum
+
+
+ Tugniwin yettwaskelsen akked yettwabḍan <b>ur ttwakkasent ara</b> mi ara ad tekkseḍ %1$s azray.
+
+
+
+ Asentel
+
+ Aceɛlal
+
+ Aberkan
+
+ Asbadu n uḥraz n uẓru
+
+ Ḍfer asntel n yibnek
+
+
+
+ Asmel-a ur issefrak ara HTTPS
+
+
+ Issin ugar
+ Beddel aɣewwaṛ-a deg Iɣewwaṛen > Tabaḍnit & Taɣellist.]]>
+
+
+ Tuqqna d taraɣellsant
+
+
+
+ Ma teqqneḍ ɣer uqeddac-a akken ilaq yakan, tuccḍa tezmer ad tili d taskudant.
+ ]]>
+
+
+ Ahat yella ḥedd i iɛerrḍen ad yaker tamagit n usmel, ur ilaq ara ad tkemmleḍ.
+
+%1$s ur yumin ara %2$s acku tasiregt d tarussint, tasiregt tesɛa azmul-is, neɣ aqeddac ur d-yuzin ara tisirag timeɣta.
+ ]]>
+
+
+
+ Mdel Iccer
+
+
+
+ Ṭṭfeɣ-ten! Nesseḥbes asmel-a ɣef tɛessast-ik·im. Sit ɣef ẓẓerb-nni yal tikkelt i wakken ad twaliḍ acu i nessewḥel.
+
+
+ Mdel asfaylu udhim
+
+
+
+ Tettummestneḍ!
+
+ Iɣewwaren-a s wudem amezwer ttmuddun ammesten iǧehden. Maca yeshel ad ttubeddlen, ad ten-terreḍ akken i tebɣiḍ.
+
+ Zgel
+
+
+ Sit dagi i wakken ad tḍeggreḍ kullec — azray, inagan n tuqqna, kullec — syen bdu seg tazwara qef yiccer amaynut.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Mdel
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Nadi awiǧit
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Azray n tunigin yettwasfaḍ! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Bdu tiɣimit-ik·im n tunigin tusligt, ad nessewḥal ineḍfaren d yir tɣawsiwin simi tettedduḍ.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Ad ak·akem-neǧǧ tunigin tusligt, maca bdu s zzerb deg wussan i d-iteddun s uwiǧit %1$s ɣef ugdil-ik·im agejdan.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Rnu awiǧit ɣer ugdil agejdan
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Yettwarna uwiǧit ɣer ugdil agejdan
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-kk/strings.xml b/mobile/android/focus-android/app/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000000..f3d7a9e74c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-kk/strings.xml
@@ -0,0 +1,1117 @@
+
+
+
+
+
+
+
+
+ Бас тарту
+
+ ОК
+
+ Сақтау
+
+
+ Іздеу немесе адрес
+
+ Автоматты түрдегі жекелік шолу.\nШолу. Өшіру. Қайталау.
+
+
+ Сіздің шолу тарихыңыз өшірілді.
+ Шолу тарихы тазартылды
+
+
+ Беттің шолу тарихы өшірілді.
+
+
+ %1$s іздеу
+
+
+ Бөлісу…
+
+
+ Сайт мәселесі жөнінде хабарлау
+
+
+ %1$s көмегімен ашу
+
+
+ Көмегімен ашу…
+
+
+ Үй экранына қосу
+
+
+ Жарлықтарға қосу
+
+ Жарлықтардан өшіру
+
+
+ Баптаулар
+ Осы туралы
+ Көмек
+ Құқықтарыңыз
+
+
+ Блокталған трекерлер
+
+
+ Бұл мүмкіндікті өшіру кейбір сайт мәселелерін шешуі мүмкін
+
+
+ Құраманы бұғаттау
+
+ Кейбір сайттарды жөндеу үшін өшіру
+
+
+ %1$s негізінде
+
+
+ Арқылы бөлісу
+
+ Шолу тарихын өшіру керек пе?
+
+ Шолу журналын қауіпсіз түрде өшіру үшін осы хабарландыруды шертіңіз немесе тазартыңыз.
+
+
+ Шолу журналын қауіпсіз түрде өшіру үшін осы хабарландыруды шертіңіз немесе сырғытыңыз.
+
+ Шолу тарихын өшіру
+
+
+ Ашу
+
+
+ Өшіру және ашу
+
+
+ Өшіру
+
+
+ Шолу тарихын өшіру
+
+
+
+ Өшіру және ашу
+
+
+ Өшіру және %1$s ашу
+
+
+
+ Focus көмегімен іздеу
+
+ Klar көмегімен іздеу
+
+ Focus Beta көмегімен іздеу
+
+ Focus Nightly көмегімен іздеу
+
+
+ %1$s басқаруды қолыңызға береді.
+Оны жекелік браузері ретінде қолданыңыз:
+
+ Қолданба ішінен тікелей іздеу және шолу
+ Трекерлерді блоктау (немесе, оларды рұқсат ету үшін, баптауларды жаңарту)
+ Cookie файлдарын және іздеу мен шолу тарихын кетіру үшін өшіру
+
+
+%1$s жасаған Mozilla. Мақсатымыз - интернет денсаулығын және ашықтығын сақтау.
+Көбірек білу
]]>
+
+
+ Жекелік және қауіпсіздік
+
+
+ Бақылау, cookies файлдары, деректер таңдауы
+
+
+ Бастапқы мәні, автотолықтыру
+
+
+
+
+ %1$s туралы, көмек
+
+
+ Бақылаудан кеңейтілген қорғаныс
+
+
+ Веб құрамасы
+
+
+ Қолданбаларды ауыстыру
+
+
+ Жалпы
+
+
+ Негізгі браузер, тіл
+
+
+ Деректерді жинау және қолдану
+
+ Іздеу
+
+
+ Іздеу ұсыныстарын алу
+
+ %1$s сіз адрестік жолақта терген нәрсені сіздің іздеу жүйеңізге жіберетін болады
+
+
+ Негізгі
+
+
+ Іздеу жүйесі
+
+
+ Іске қосулған
+
+
+ Сөндірілген
+
+
+ URL автотолықтыру
+
+
+ Үздік сайттар үшін
+
+
+ %s адрестік жолақта 450-ден астам әйгілі URL-ді автотолықтыру үшін іске қосыңыз.
+
+
+ Қосылатын сайттар үшін
+
+
+ %s сіздің таңдамалы URL автотолықтыруды іске қосу.
+
+
+ Сайттарды басқару
+
+
+ Сайттарды басқару
+
+
+ + Таңдауыңызша URL қосу
+
+
+ Сіздің автотолтыру тізіміңіз:
+
+
+ URL қосу
+
+
+ Таңдауыңызша URL қосу
+
+
+ Таңдауыңызша URL қосу
+
+
+ Автотолықтыру үшін сілтемені қосу
+
+
+ Cookies файлдары және сайт деректері
+
+
+ Деректер таңдауы
+
+
+ Таңдауыңызша URL-дер өшіру
+
+
+ Көбірек білу
+
+
+ Таңдауыңызша автотолықтыру URL-ді қосыңыз немесе басқарыңыз.
+
+
+ Қосылатын URL
+
+
+ URL кірістіріңіз немесе енгізіңіз
+
+
+ Мысалы: mozilla.org
+
+
+ Мысалы: example.com
+
+
+ Жаңа таңдауыңызша URL қосылды.
+
+
+ Өшіру
+
+
+ Өшіру
+
+
+ Енгізілген URL тексеріңіз.
+
+ Тіл
+
+ Жүйелік
+
+ Жекелік
+ Жарнама трекерлерін блоктау
+ Кейбір жарналамалар сайт шолуды бақылайды, жарнамаларға шертпесеңіз де
+ Аналитикалық трекерлерді блоктау
+ Шерту және айналдыру әрекеттерін жинау, зерттеу және өлшеуге пайдаланылады
+ Әлеуметтік желілер трекерлерін блоктау
+ Шолуыңызды бақылау және бөлісу батырмалары сияқты мүмкіндіктерді көрсету мақсатымен сайттарға ендірілген
+ Басқа құрама трекерлерін блоктау
+ Бұны іске қосу кейбір сайттардың мінез-құлығын күтпегендей өзгертеді
+ Cookies файлдарын бұғаттау
+
+
+ Жоқ, рахмет
+ Тек үшінші жақты cookies файлдарын бұғаттау
+ Тек үшінші жақты cookies файлдарын бұғаттау
+ Сайтаралық cookie файлдарын бұғаттау
+ Иә
+
+
+ Қолданбаны босату үшін саусақ баспасын қолдану
+
+
+ Жарлықтарды қосқан болсаңыз немесе веб-сайт %s ішінде қазір ашық болса, саусақ ізі арқылы құлыпты ашыңыз.
+
+
+ Жасырын
+
+ Қолданбаларды ауыстыру кезінде веб парақтарды жасыру және скриншот түсіруді бұғаттау.
+
+ Қауіпсіздік
+
+ Өнімділік
+ Веб қаріптерін блоктау
+
+ Жоқ болып тұрған таңбашалар немесе суреттерге әкеп соғуы мүмкін
+
+ JavaScript бұғаттау
+
+ Беттер жылдамдау жүктелуі мүмкін, бірақ, мінез-құлығы күтпегендей өзгеруі мүмкін
+
+
+ %1$s-ты негізгі браузер қылу
+
+ Mozilla
+ Қолдану деректерін жіберу
+
+
+ Көбірек білу
+
+
+ Mozilla тек %1$s әркім үшін ұсыну және жақсарту мақсатында керек деректерді жинауға тырысады.
+
+
+ Жекелік ескертуі
+
+
+ Лицензиялық ақпарат
+
+
+ Біз пайдаланатын библиотекалар
+
+
+ %s | Ашық библиотекалар
+
+
+ %1$s туралы
+
+
+ Орнатылған іздеу жүйелері
+
+
+ Іздеу жүйесін таңдау
+
+
+ Үнсіз келісім іздеу жүйелерін қалпына келтіру
+
+
+ + Басқа іздеу жүйесін қосу
+ Іздеу жүйелерін өшіру
+ Өшіру
+
+ Басқа іздеу жүйесін қосу
+
+ Өзіңіз қалайтын іздеу жүйесін таңдаңыз:
+
+
+ Іздеу жүйесін қосу
+
+ Іздеу жүйесінің аты
+ Қолданылатын іздеу жолы
+ Сақтау
+
+
+ Мысалы: example.com/search/?q=%s
+
+ Жаңа іздеу жүйесі қосылды.
+
+ Іздеу жүйесі атын енгізіңіз
+ Орнатылған іздеу жүйесі бұл атын қолданып тұр.
+
+ Іздеу жолын енгізіңіз
+
+ Іздеу жолы мысал пішіміне сай келетінін тексеріңіз
+
+
+ Енгізуді тазарту
+
+
+ Тайдыру
+
+
+ Шолу тарихын өшіру
+
+
+ Ашық беттер: %1$s
+
+
+ Қауіпсіз байланыс
+
+
+ Жүктелуде
+
+
+ Вебсайт жүктелген
+
+
+ Көбірек опциялар
+
+
+ Қосымша опциялар батырмасы
+
+
+ Алға өту
+
+
+ Вебсайтты қайта жүктеу
+
+
+ Артқа өту
+
+
+ Вебсайтты жүктеуді тоқтату
+
+
+ Алдыңғы қолданбаға оралу
+
+
+ Блокталған трекерлер саны
+
+
+ Трекерлерді блоктау
+
+ Құқықтарыңыз
+
+ Сілтемені басқа қолданбада ашу
+
+ Бұл сілтемені %2$s көмегімен ашу үшін, сіз %1$s ішінен шыға аласыз.
+
+ Сілтемені аша алатын қолданбаны табу
+
+ Құрылғыңыздағы бірде-бір қолданба бұл сілтемені аша алмайды. Оны аша алатын қолданбасын %2$s ішінен іздеу үшін, сіз %1$s ішінен шыға аласыз.
+
+ Жекелік шолу режимінен шығу керек пе?
+
+
+ %1$s аяқталған
+
+
+ Ашу
+
+
+
+
+
+
+
+
+
+
+ Жарлықтарға қосылды!
+
+ Сервер табылмады
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Жабу
+
+
+
+ %1$s өніміне қош келдіңіз
+
+
+ Жылдам. Жеке. Ешқандай алаңдаушылығы жоқ.
+
+
+ Бастау
+
+
+
+ %1$s басқа браузерлер сияқты емес
+
+
+ Қосымша жекелік үшін сіз қолданбаны жапқан кезде тарихыңызды тазартамыз.
+
+
+
+ Әрбір ашылған сілтеме кезінде деректеріңізді қорғау үшін %1$s өнімін негізгі етіп орнатыңыз.
+
+
+ Негізгі браузер қылу
+
+
+ Аттап кету
+
+
+
+ Жекелігіңізді күшейтіңіз
+
+ Жекелік шолудың келесі деңгейіне өтіңіз. Сайттар арасында сізді бақылайтын және парақтардың жүктелу уақытын арттыратын жарнама және басқа құраманы блоктаңыз.
+
+
+ Сіздің іздеулеріңіз, сіздің жолыңызбен
+
+ Басқа бірнәрсе іздедіңіз бе? Баптаулардан басқа негізгі іздеу жүйесін таңдаңыз.
+
+
+ Үй экранына жарлықтарды қосыңыз
+
+ %1$s ішінде таңдамалы сайттарға жылдам оралыңыз. Тек %1$s мәзірінен \"Үй экранына қосу\" таңдаңыз.
+
+
+ Жекелікті әдет қылыңыз
+
+ %1$s негізгі браузер ретінде орнатып, басқа қолданбалардан вебпарақтарын ашу кезінде жекелік шолудың артықшылықтарын алыңыз.
+
+ Жақсы, түсіндім!
+ Аттап кету
+ Келесі
+
+
+ -
+
+
+ Қосу
+
+ ИӘ
+
+
+ Бас тарту
+
+ ЖОҚ
+
+
+ Жарлық Бақылаудан жақсартылған қорғанысы сөндірілген түрімен ашылады
+
+
+ Жекелік шолу сессиясы
+
+
+ Хабарламалар сізге %1$s сессиясын бір шертумен өшіруді мүмкін етеді. Сізге браузерді ашуды немесе браузерде не орындалғанын көру керек емес.
+
+
+ Шолу тарихын өшіру
+
+
+ Firefox-ты жүктеп алу
+
+
+
+
+
+
+
+
+ Mozilla публикалық лицензиясы және басқа кодтары ашық лицензиялар аясында қолжетімді етілген.]]>
+
+
+ осы жерден табуға болады.]]>
+
+
+ лицензиялар аясында қолжетімді.]]>
+
+
+ GNU General Public License v3 аясында, осында бөлек және тәуелсіз жұмыстар ретінде қолжетімді.]]>
+
+
+ Пайдаланушы аты
+ Пароль
+ Тазарту
+
+
+
+ Қауіпсіз байланыс
+ Қауіпсіз емес байланыс
+
+ Растаған: %1$s
+
+
+ Сайт қауіпсіздігі
+ URL бар болып тұр
+
+
+ Беттен табу
+
+
+ Беттен табу
+
+
+ %1$d/%2$d
+
+ %2$d ішінен %1$d
+
+
+ Келесі нәтижені табу
+
+ Алдыңғы нәтижені табу
+
+ Беттен іздеуді алып тастау
+
+
+
+
+ Сайттың толық нұсқасы
+
+
+ Жұмыс үстелі сайты
+
+
+ URL сілтемесі көшірілді
+
+
+ Әзірлеуші құралдары
+
+
+ Сілтемелерді қолданбаларда ашу
+
+
+ Кеңейтілген
+
+
+ Сайт рұқсаттары
+
+
+ Cookie баннерлерін азайту
+
+
+ Іске қосулы
+
+
+ Сөндірулі
+
+
+ Cookie баннерлерін азайту
+
+
+ Cookie сұрауларын мүмкіндігінше автоматты түрде қабылдамау арқылы баннерлерді азырақ көріңіз.
+
+ -->
+ Cookie баннерлерін азайту
+
+
+ Бұл сайт үшін іске қосылған
+
+
+ Сайтқа ағымдағы уақытта қолдау жоқ
+
+
+ Бұл сайт үшін сөндірілген
+
+
+ Cookie баннерлерін азайту
+
+
+ Бұл сайт үшін сөндірілген
+
+
+ Бұл сайт үшін іске қосылған
+
+
+ %1$s үшін cookie баннерлерін азайту мүмкіндігін іске қосу керек пе?
+
+
+ %1$s үшін cookie баннерлерін азайту мүмкіндігін сөндіру керек пе?
+
+
+ %1$s осы сайттың cookie файлдарын тазартып, бетті жаңартады. Барлық cookie файлдарын тазарту салдарынан сіз сайттан шығуыңыз мүмкін немесе дүкен себеттері тазартылуы мүмкін.
+
+
+ %1$s cookie сұрауларын автоматты түрде қабылдамау әрекетін жасай алады.
+
+
+ Бұл сайтқа қазіргі уақытта Cookie баннерлерін азайту қолдау көрсетпейді. Біздің топтан осы веб-сайтты қарап шығуды және болашақта қолдау көрсетуді сұрағыңыз келе ме?
+
+
+ Бас тарту
+
+
+ Қолдау сұрау
+
+
+ Сайтқа қолдау көрсетуге сұраным жіберілді.
+
+
+ Сайтқа қолдау көрсетуге сұраным жіберілді.
+
+
+
+ %1$s мазаңызды алатын cookie баннерлерін болдырмау үшін cookie сұранымдарын қабылдамау талабын жасайды.\n\n%2$s ішінде cookie баннерлері опцияларын өзгертуге болады.
+
+
+ баптаулар
+
+
+ Автоойнату
+
+
+ Рұқсат ету үшін:
+
+
+ 1. Android баптауларына өтіңіз
+
+
+ Рұқсаттар басыңыз]]>
+
+
+ Баптауларға өтіңіз
+
+
+ %1$s ауыстырғышын іске қосыңыз]]>
+
+
+ Камера
+
+
+ Микрофон
+
+
+ Орналасу
+
+
+ Ескерту
+
+
+ DRM-мен басқарылатын құрама
+
+
+ Рұқсат ету үшін сұрау
+
+
+ Бұғатталған
+
+
+ Рұқсат етілген
+
+
+ Android бұғаттаған
+
+
+ Аудио мен видеоны рұқсат ету
+
+
+ Тек аудионы бұғаттау
+
+
+ Ұсынылатын
+
+
+ Аудио мен видеоны бұғаттау
+
+
+ Зерттеулер
+
+
+ Firefox кейде зерттеулерді орнатып, орындай алады.
+
+
+ Көбірек білу
+
+
+ Қолданба өзгерістерді іске асыру үшін жабылады
+
+
+ Өшіру
+
+
+ Белсенді
+
+
+ Аяқталған
+
+
+ USB/Wi-Fi арқылы қашықтан жөндеу
+
+
+ Босату
+
+
+ Саусақ ізін пайдаланап растаңыз
+
+
+ Ағымдағы қолданба сеансын жалғастыру үшін саусақ ізін пайдалануға болады.
+
+
+ Сілтемені жаңа сессияда ашу
+
+
+ Саусақ баспасы таңбашасы
+
+
+ Саусақ баспасы танылмады. Қайталап көріңіз.
+
+
+ Саусақ тым жылдам өткізілді. Қайталап көріңіз.
+
+
+ Іздеу ұсыныстарын көрсету керек пе?
+
+
+ Іздеу ұсыныстарын алу үшін, %1$s сіз адрестік жолақта терген нәрсені іздеу жүйесіне жіберуі тиіс.
+
+
+ Жоқ
+
+
+ Иә
+
+
+ Кейбір іздеу жүйелері ұсыныстарды көрсете алмайды.
+
+
+ Тайдыру
+
+
+
+
+ Әлі де күтпегендей әрекет жасайды ма?\n Бақылаудан қорғанысты сөндіріп көріңіз
+
+
+ Үй бетіне қосу]]>
+
+
+ Әр сілтемені %1$s ішінде ашыңыз\n %1$s негізгі браузер ретінде орнатыңыз
+
+
+ Жиірек қолданылатын сайттар үшін сілтемелерді автотолықтырыңыз\n Адрестік жолақта кез келген сілтемеге ұзақ шертіңіз
+
+
+ Сілтемені жаңа бетте ашу\n Беттегі кез келген сілтемеге ұзақ шертіңіз
+
+
+ Іске қосу экранында кеңестерді сөндіру
+
+
+ Жаңа бет ашылды
+
+
+ Ауысу
+
+
+ Толық экран режиміне өту
+
+
+ Жаңа бетке тура ауысу
+
+
+ Қауіпті және алдамшы болуы мүмкін сайттарды блоктау
+
+ Хабарланған алмамшы, шабуылшы, зиянкес және қаламайтын БҚ орнататын сайттарды бұғаттау.
+
+
+ Тек-HTTPS режимі
+
+
+ Қауіпсіздікті арттыру үшін сайттарға HTTPS шифрлеу хаттамасын пайдаланып автоматты түрде қосылу әрекетін жасайды.
+
+
+ Ережеден бөлек
+
+ Бұл веб-сайттар үшін құраманы бұғаттауды сөндіргенсіз.
+
+ Өшіру
+
+ Барлық веб-сайттарды өшіру
+
+
+ Cookies файлдарын бұғаттау
+
+
+ Cookie файлдарын бұғаттауды қалайсыз ба?
+
+
+ Бет құлады
+
+ Кешіріңіз. Бұл бетпен мәселе бар.
+
+ Жеке браузер ретінде ешқашан сақтамаймыз және осы бетті қалпына келтіре алмаймыз.
+
+ Бетті жабу
+
+
+
+
+
+ Mozilla-ға құлау хабарламасын жіберу
+
+
+
+
+ %s бастап, бұғатталған трекер саны
+
+ Мазмұн
+
+ Жарнама
+
+ Әлеуметтік
+
+ Аналитика
+
+ Бақылаудан кеңейтілген қорғаныс
+
+ Бұл сайт үшін қорғаныс СӨНДІРІЛГЕН
+
+ Бұл сайт үшін қорғаныс ІСКЕ ҚОСЫЛҒАН
+
+ Байланыс қауіпсіз
+
+ Байланыс қауіпсіз емес
+
+ Бұғаттау үшін трекерлер және скрипттер
+
+
+ Артқа
+
+
+
+ Өшіру
+
+
+ Атын өзгерту
+
+ Атын өзгерту
+
+
+ Жарлық атауы
+
+
+ %1$s тарихы өшірілген кезде сақталған және бөліскен суреттер <b>өшірілмейді</b>
+
+
+
+ Тема
+
+ Ашық түсті
+
+ Күңгірт
+
+ Батареяны үнемдеу режимі арқылы орнатылды
+
+ Құрылғы темасына сүйену
+
+
+
+ Бұл сайт HTTPS хаттамасын қолдамайды
+
+
+ Көбірек білу
+ Бұл баптауды Баптаулар > Жекелік және қауіпсіздік > Қауіпсіздік ішінде өзгертуге болады.]]>
+
+
+ Байланыс қауіпсіз емес
+
+
+
+ Осыған дейін осы серверге сәтті қосылған болсаңыз, осы қате уақытша болуы мүмкін.
+ ]]>
+
+
+ Біреу бұл сайтты еліктеу талабын жасап жатқаны мүмкін, сондықтан жалғастыру қауіпті болуы мүмкін.
+
+ %1$s қолданбасы %2$s адресіне сенбейді, өйткені сертификат шығарушысы белгісіз, сертификат қолтаңбасы өздігінен қойылған, немесе сервер жарамды аралық сертификаттарды жібермеген.
+ ]]>
+
+
+
+ Бетті жабу
+
+
+
+ Ұстадық! Біз бұл сайттың сізге тыңшылық жасауын тоқтаттық. Біз блоктаған нәрселер туралы ақпарат алу үшін қалқанды шертіңіз.
+
+
+ Қалқымалы терезені жабу
+
+
+
+ Сіз қорғалғансыз!
+
+ Бұл бастапқы баптаулар қатаң қорғаныс ұсынады. Бірақ нақты қажеттіліктеріңізді қанағаттандыру үшін баптауларды өзгерту оңай.
+
+ Тайдыру
+
+
+ Барлығын қоқысқа тастау үшін осында шертіңіз — тарих, cookie файлдары, барлығы — және жаңа беттен жаңадан бастаңыз.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Жабу
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Іздеу виджеті
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Шолу тарихы тазартылды! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Жекелік шолу сеансын бастаңыз, сонда біз трекерлерді және басқа да жаман нәрселерді бұғаттаймыз.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Біз сізді жеке шолуға қалдырамыз, бірақ келесі жолы негізгі экрандағы %1$s виджетімен жылдамырақ бастауға болады.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Виджетті үй экранына қосу
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Виджетті үй экранына қосылды
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ko/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000000..53916f0c37
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ko/strings.xml
@@ -0,0 +1,1117 @@
+
+
+
+
+
+
+
+
+ 취소
+
+ 확인
+
+ 저장
+
+
+ 검색어 또는 주소 입력
+
+ 자동화된 개인 정보 보호 브라우저.\n쓰고, 지우고. 쓰고, 지우고.
+
+
+ 페이지 탐색 기록이 삭제되었습니다.
+ 방문 기록 삭제됨
+
+
+ 탭의 탐색 기록이 삭제되었습니다.
+
+
+ %1$s 검색
+
+
+ 공유…
+
+
+ 사이트 이슈 보고
+
+
+ %1$s로/으로 열기
+
+
+ 다른 브라우저로 열기
+
+
+ 초기 화면에 추가하기
+
+
+ 바로가기 추가
+
+ 바로가기에서 제거
+
+
+ 설정
+ 정보
+ 도움말
+ 사용자 권리
+
+
+ 차단된 추적기
+
+
+ 이 기능을 끄면 일부 사이트 문제가 해결 될 수 있습니다.
+
+
+ 콘텐츠 차단
+
+ 일부 사이트 문제 해결을 위해 끄기
+
+
+ %1$s에서 지원
+
+
+ 다음으로 공유
+
+ 방문 기록을 삭제하시겠습니까?
+ 검색 기록을 안전하게 삭제하려면 이 알림을 탭하거나 지우세요.
+
+
+ 이 알림을 탭하거나 스와이프하면 인터넷 사용 기록이 안전하게 삭제됩니다.
+
+ 방문 기록 지우기
+
+
+ 열기
+
+
+ 지우고 열기
+
+
+ 지우기
+
+
+ 방문 기록 지우기
+
+
+
+ 지우고 열기
+
+
+ %1$s 지우고 열기
+
+
+
+ Focus에서 검색
+
+ Klar에서 검색
+
+ Focus Beta에서 검색
+
+ Focus Nightly에서 검색
+
+
+ %1$s를 여러 기능으로 사용할 수 있습니다.
+개인 정보 보호 브라우저로 사용:
+
+ 앱에서 바로 검색과 브라우징 하기
+ 추적기 차단(설정을 변경해서 허용할 수 있음)
+ 쿠키나 검색어, 브라우징 기록을 삭제
+
+
+%1$s는 Mozilla의 제품입니다. Mozilla의 사명은 건강하고 개방적인 인터넷을 조성하는 것입니다.
+더 알아보기
]]>
+
+
+ 개인정보 보호 및 보안
+
+
+ 추적, 쿠키, 데이타 선택
+
+
+ 기본 설정, 자동 완성
+
+
+
+
+ %1$s 정보, 도움말
+
+
+ 향상된 추적 방지 기능
+
+
+ 웹 콘텐츠
+
+
+ 앱 전환
+
+
+ 일반
+
+
+ 기본 브라우저, 언어
+
+
+ 데이터 수집 및 사용
+
+ 검색
+
+
+ 추천 검색어 켜기
+
+ 주소창에 입력을 하면 %1$s가 이를 검색 엔진에 보냄
+
+
+ 기본
+
+
+ 검색 엔진
+
+
+ 사용 중
+
+
+ 사용 안 함
+
+
+ URL 자동 완성
+
+
+ 자주 가는 사이트
+
+
+ %s 자동 완성 기능을 활성화해서 450개가 넘는 자주 찾는 URL을 주소창에서 사용해 보세요.
+
+
+ 내가 추가한 사이트
+
+
+ %s 자동 완성 기능을 활성화해서 자주 찾는 URL을 사용해 보세요.
+
+
+ 사이트 관리
+
+
+ 사이트 관리
+
+
+ + 사용자 정의 URL 추가
+
+
+ 자동 완성 목록:
+
+
+ URL 추가
+
+
+ 사용자 정의 URL 추가
+
+
+ 사용자 정의 URL 추가
+
+
+ 자동 완성 링크 추가
+
+
+ 쿠키와 사이트 데이타
+
+
+ 데이타 선택
+
+
+ 사용자 정의 URL 삭제
+
+
+ 더 알아보기
+
+
+ 사용자 정의 자동 완성 URL 추가하고 관리합니다.
+
+
+ 추가할 URL
+
+
+ URL 입력 또는 붙여넣기
+
+
+ 예: mozilla.org
+
+
+ 예: example.com
+
+
+ 새 사용자 정의 URL이 추가되었습니다.
+
+
+ 삭제
+
+
+ 삭제
+
+
+ 입력한 URL을 다시 확인하세요.
+
+ 언어
+
+ 시스템 기본값
+
+ 개인 정보
+ 광고 추적기 차단
+ 특정 광고의 경우, 광고를 클릭하지 않은 경우에도 방문 기록을 추적합니다
+ 분석용 추적기 차단
+ 탭한 기록이나 스크롤과 같은 사용자 활동 내역을 수집하고 분석하는데 사용됩니다
+ 소셜 추적기 차단
+ 사용자의 방문 기록을 추적하거나 공유 버튼과 같은 기능을 표시하기 위해 사이트에 내장되어 있습니다
+ 기타 콘텐츠 추적기 차단
+ 설정 시 웹 페이지가 제대로 표시되지 않을 수 있습니다
+ 쿠키 차단
+
+
+ 아니오
+ 제 3자 추적기 쿠키 만 차단
+ 제 3자 쿠키만 차단
+ 교차 사이트 쿠키 차단
+ 네, 물론이요.
+
+
+ 잠금 헤제에 지문을 사용
+
+
+ 바로가기를 추가했거나 웹사이트가 이미 %s에서 열려 있는 경우 지문을 사용하여 잠금을 해제합니다.
+
+
+ 스텔스
+
+ 앱 전환 시 웹페이지를 감추고 스크린샷을 못찍게 합니다.
+
+ 보안
+
+ 성능
+ 웹 폰트 차단
+
+ 아이콘 또는 이미지가 누락될 수 있음
+
+ JavaScript 차단
+
+ 페이지가 더 빠르게 로드될 수 있지만 에상치 못한 동작을 할 수도 있음
+
+
+ %1$s를 기본 브라우저로 설정
+
+ Mozilla
+ 사용 정보 보내기
+
+
+ 상세정보
+
+
+ Mozilla는 %1$s 이용에 더 나은 서비스를 제공하고 향상시키는 데 오직 필요한 정보만을 수집합니다.
+
+
+ 개인 정보 보호 정책
+
+
+ 라이선스 정보
+
+
+ 사용한 라이브러리
+
+
+ %s | OSS 라이브러리
+
+
+ %1$s에 대한 정보
+
+
+ 설치된 검색 엔진 모음
+
+
+ 검색 엔진 선택
+
+
+ 기본 검색 엔진 설정 초기화
+
+
+ + 또 다른 검색 엔진 추가
+ 검색 엔진 삭제
+ 삭제
+
+ 다른 검색 엔진 추가
+
+ 선호하는 엔진 선택:
+
+
+ 검색 엔진 추가
+
+ 검색 엔진 이름
+ 사용할 검색 문구
+ 저장
+
+
+ 예: example.com/search/?q=%s
+
+ 새 검색 엔진이 추가되었습니다.
+
+ 검색 엔진 이름을 입력하세요
+ 설치된 검색 엔진이 이미 해당 이름을 사용하고 있습니다.
+
+ 검색어를 입력하세요
+
+ 검색어가 예시 형식과 일치하는 지 확인하세요
+
+
+ 입력 값 초기화
+
+
+ 취소
+
+
+ 탐색 기록 지우기
+
+
+ 열린 탭: %1$s
+
+
+ 보안 연결
+
+
+ 로딩 중
+
+
+ 웹 사이트 불러옴
+
+
+ 옵션 더보기
+
+
+ 추가 옵션 버튼
+
+
+ 앞으로 가기
+
+
+ 웹 사이트 다시 불러오기
+
+
+ 뒤로 가기
+
+
+ 웹사이트 로딩 중지
+
+
+ 이전 앱으로 돌아가기
+
+
+ 차단된 추적기 수
+
+
+ 추적기 차단
+
+ 권리
+
+ 다른 앱으로 링크 열기
+
+ %2$s로 링크를 열면 %1$s를 떠나게 됩니다.
+
+ 링크를 열 수 있는 앱 찾기
+
+ 이 링크를 열 수 있는 앱이 설치돼있지 않습니다. %1$s를 종료하고 %2$s를 열어 이 링크를 열 수 있는 앱을 찾을 수 있습니다.
+
+ 개인 정보 보호 브라우징을 종료할까요?
+
+
+ %1$s 다운로드됨
+
+
+ 열기
+
+
+
+
+
+
+
+
+
+
+ 바로가기에 추가되었습니다!
+
+ 서버를 찾을 수 없습니다
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 닫기
+
+
+
+ %1$s에 오신 것을 환영합니다
+
+
+ 빠르고. 사생활 보호되고. 방해 없는.
+
+
+ 시작하기
+
+
+
+ %1$s, 다른 브라우저와 다릅니다.
+
+
+ 추가적인 개인 정보 보호를 위해 앱을 닫으면 방문 기록이 삭제됩니다.
+
+
+
+ 링크를 열 때마다 내 데이터를 보호하려면 %1$s를 기본 브라우저로 설정하세요.
+
+
+ 기본 브라우저로 설정하기
+
+
+ 건너뛰기
+
+
+
+ 개인정보 보호 강화하기
+
+ 한 단계 높은 개인 정보 보호 브라우징을 즐겨보세요. 사이트의 경계를 넘어 개인정보를 추적하거나, 페이지 로딩 속도를 늦추는 원인이 되는 광고 및 기타 콘텐츠를 자동으로 차단해 줍니다.
+
+
+ 검색을 내 입맛대로
+
+ 다른 방식으로 검색을 원하세요? 설정에서 다른 검색 엔진을 기본값으로 선택할 수 있습니다.
+
+
+ 초기 화면에 바로 가기 추가
+
+ %1$s에서는 자주 방문하는 사이트로 빠르게 이동할 수 있습니다. %1$s 메뉴에서 \"초기 화면에 추가하기\" 를 이용하세요.
+
+
+ 사생활 보호를 습관처럼
+
+ %1$s 를 기본 브라우저로 설정하시면, 다른 앱에서 웹페이지를 열어볼 때 언제든지 개인 정보 보호의 혜택을 누릴 수 있습니다.
+
+ 알겠습니다!
+ 건너뛰기
+ 다음
+
+
+ -
+
+
+ 추가
+
+ 네
+
+
+ 취소
+
+ 아니오
+
+
+ 바로 가기가 향상된 추적 방지 기능이 해제된 상태에서 열림
+
+
+ 개인 정보 보호 브라우징 세션
+
+
+ 알림에서는 %1$s 세션을 탭 한번으로 삭제할 수 있습니다. 앱을 실행하거나 브라우저에 열려 있는 세션을 확인할 필요가 없습니다.
+
+
+ 방문 기록 삭제
+
+
+ Firefox 다운로드
+
+
+
+
+
+
+
+
+ Mozilla 공개 라이선스 및 기타 다른 오픈 소스 라이선스 이용 약관에 따라 제공됩니다.]]>
+
+
+ 다음을 참고하시기 바랍니다.]]>
+
+
+ 라이선스 계약에 따라 제공됩니다.]]>
+
+
+ GNU General Public License v3를 바탕으로 자체적으로 별도로 관리하는 차단목록을 사용합니다. 자세한 내용은 다음 에서 확인할 수 있습니다.]]>
+
+
+ 사용자명
+ 비밀번호
+ 지우기
+
+
+
+ 안전한 연결
+ 안전하지 않은 연결
+
+ 인증: %1$s
+
+
+ 사이트 보안
+ URL이 이미 존재함
+
+
+ 페이지내 검색
+
+
+ 페이지내 검색
+
+
+ %1$d/%2$d
+
+ %2$d 중 %1$d
+
+
+ 다음 결과 찾기
+
+ 이전 결과 찾기
+
+ 페이지에서 찾기 해제
+
+
+
+
+ 데스크톱 사이트 요청
+
+
+ 데스크톱 사이트
+
+
+ URL 복사됨
+
+
+ 개발자 도구
+
+
+ 앱에서 링크 열기
+
+
+ 고급
+
+
+ 사이트 권한
+
+
+ 쿠키 배너 줄이기
+
+
+ 켜기
+
+
+ 끄기
+
+
+ 쿠키 배너 줄이기
+
+
+ 가능한 쿠키 요청을 자동으로 거부하여 배너를 덜 노출시킵니다.
+
+ -->
+ 쿠키 배너 줄이기
+
+
+ 이 사이트에서 켜짐
+
+
+ 현재 지원되지 않는 사이트
+
+
+ 이 사이트에서 꺼짐
+
+
+ 쿠키 배너 줄이기
+
+
+ 이 사이트에서 꺼짐
+
+
+ 이 사이트에서 켜짐
+
+
+ %1$s에서 쿠키 배너 줄이기를 켜시겠습니까?
+
+
+ %1$s에서 쿠키 배너 줄이기를 끄시겠습니까?
+
+
+ %1$s가 이 사이트의 쿠키를 지우고 페이지를 새로고침할 것입니다. 모든 쿠키를 삭제하면 로그아웃되거나 장바구니 등이 비워질 수 있습니다.
+
+
+ %1$s는 쿠키 요청을 자동으로 거부할 수 있습니다.
+
+
+ 이 사이트는 현재 Cookie Banner Reduction이 지원되지 않습니다. 저희 팀이 이 웹사이트를 검토하고 향후 지원을 추가하도록 요청하시겠습니까?
+
+
+ 취소
+
+
+ 지원 요청
+
+
+ 사이트 지원 요청이 제출되었습니다.
+
+
+ 사이트 지원 요청이 제출되었습니다.
+
+
+
+ %1$s는 성가신 쿠키 배너를 닫기 위해 쿠키 요청을 거부하려고 시도합니다.\n\n%2$s에서 쿠키 배너 설정을 관리하세요.
+
+
+ 설정
+
+
+ 자동 재생
+
+
+ 허용하려면:
+
+
+ 1. Android 설정으로 이동
+
+
+ 권한을 탭]]>
+
+
+ 설정으로 이동
+
+
+ %1$s를 켜기로 전환]]>
+
+
+ 카메라
+
+
+ 마이크
+
+
+ 위치
+
+
+ 알림
+
+
+ DRM 제어 콘텐츠
+
+
+ 허용 요청
+
+
+ 차단됨
+
+
+ 허용됨
+
+
+ Android에 의해 차단됨
+
+
+ 오디오 및 비디오 허용
+
+
+ 오디오만 차단
+
+
+ 추천
+
+
+ 오디오 및 비디오 차단
+
+
+ 연구
+
+
+ Firefox가 때때로 연구를 설치하고 실행할 수 있습니다.
+
+
+ 자세히 알아보기
+
+
+ 변경 사항을 적용하기 위해 애플리케이션이 종료됩니다
+
+
+ 삭제
+
+
+ 활성화
+
+
+ 완료
+
+
+ USB/Wi-Fi 원격 디버깅
+
+
+ 잠금 해제
+
+
+ 지문을 사용하여 확인
+
+
+ 지문을 사용하여 현재 앱 세션을 계속할 수 있습니다.
+
+
+ 새 세션에서 링크 열기
+
+
+ 지문 아이콘
+
+
+ 지문이 인식되지 않았습니다. 다시 시도해 주세요.
+
+
+ 손가락을 너무 빨리 움직였습니다. 다시 시도해 주세요.
+
+
+ 추천 검색을 사용할까요?
+
+
+ 추천 검색을 사용하려면 %1$s가 주소창에 입력한 내용을 검색 엔진에 보내야 합니다.
+
+
+ 아니오
+
+
+ 네
+
+
+ 일부 검색 엔진은 추천을 표시할 수 없습니다.
+
+
+ 닫기
+
+
+
+
+ 사이트가 잘 작동하지 않나요?\n 추적 방지를 꺼보세요
+
+
+ 홈 화면에 추가]]>
+
+
+ 모든 링크를 %1$s에서 엽니다\n %1$s를 기본 브라우저로 설정
+
+
+ 자주 사용하는 사이트 URL을 자동 완성합니다\n 주소창에서 URL을 길게 눌러 보세요
+
+
+ 링크를 새 탭에서 엽니다\n 페이지의 링크를 길게 눌러 보세요
+
+
+ 시작 화면에서 도움말 끄기
+
+
+ 새 탭 열림
+
+
+ 전환
+
+
+ 전체 화면 모드로 들어가기
+
+
+ 새탭의 링크로 즉시 전환
+
+
+ 잠재적으로 위험한 사이트 및 사기성 사이트 차단
+
+ 사기성 사이트나 공격 사이트, 악성코드 사이트, 원치 않는 소프트웨어 사이트를 차단합니다.
+
+
+ HTTPS 전용 모드
+
+
+ 보안 강화를 위해 HTTPS 암호화 프로토콜을 사용하여 사이트에 자동으로 연결을 시도합니다.
+
+
+ 예외
+
+ 이 사이트에 대한 콘텐츠 차단을 비활성화 했습니다.
+
+ 삭제
+
+ 모든 웹사이트 삭제
+
+
+ 쿠키 차단
+
+
+ 쿠키를 차단하시겠습니까?
+
+
+ 오류난 탭
+
+ 죄송합니다. 이 탭에 문제가 있습니다.
+
+ 사생활 보호 브라우저여서 저장하지 않았기 때문에 이 탭을 복원할 수 없습니다.
+
+ 탭 닫기
+
+
+
+
+
+ Mozilla에 충돌 보고서 보내기
+
+
+
+
+ %s 이후에 차단된 추적기
+
+ 콘텐츠
+
+ 광고
+
+ 소셜
+
+ 분석
+
+
+ 향상된 추적 방지 기능
+
+ 이 사이트에서 보호 꺼짐
+
+ 이 사이트에서 보호 켜짐
+
+ 연결이 안전함
+
+ 연결이 안전하지 않음
+
+ 차단할 추적기 및 스크립트
+
+
+ 뒤로 가기
+
+
+
+ 제거
+
+
+ 이름 변경
+
+ 이름 변경
+
+
+ 바로 가기 이름
+
+
+ %1$s 기록을 지울 때 저장 및 공유된 이미지는 <b>삭제되지 않습니다</b>.
+
+
+
+ 테마
+
+ 밝은
+
+ 어두운
+
+ 절전 모드로 설정
+
+ 기기 테마 따르기
+
+
+
+ 이 사이트는 HTTPS를 지원하지 않습니다.
+
+
+ 자세히 알아보기
+ 설정 > 개인 정보 보호 & 보안 > 보안에서 이 설정을 바꿀 수 있습니다.]]>
+
+
+ 안전하지 않은 연결
+
+
+
+ 과거에 이 서버에 성공적으로 연결했다면 일시적인 오류일 수 있습니다.
+ ]]>
+
+
+ 누군가가 사이트를 사칭하려고 할 수 있으며 계속 진행하는 것은 위험할 수 있습니다.
+
+ %1$s는 인증서 발급자를 알 수 없거나 인증서가 자체 서명되었거나 서버가 올바른 중간 인증서를 전송하지 않기 때문에 %2$s 를 신뢰하지 않습니다.
+ ]]>
+
+
+
+ 탭 닫기
+
+
+
+ 잡았다! %@가 사용자를 감시하는 이 사이트를 중지했습니다. 차단된 항목에 대한 정보를 보려면 방패를 탭하세요.
+
+
+ 팝업 닫기
+
+
+
+ 보호되고 있습니다!
+
+ 이러한 기본 설정은 강력한 보호 기능을 제공합니다. 하지만 특정 요구 사항에 맞게 설정을 쉽게 수정할 수도 있습니다.
+
+ 닫기
+
+
+ 여기를 클릭하여 방문 기록, 쿠키를 포함한 모든 것을 삭제하고 새 탭을 시작하세요.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ 닫기
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ 위젯 검색하기
+
+ !-- This is the title of promote search widget dialog. -->
+
+ 웹 사이트 방문 기록이 삭제되었습니다! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 비공개 브라우저 세션을 시작해서 추적기 등 위험한 항목들을 차단하세요.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 개인 정보 보호 브라우저를 쓸 수 있지만, 홈 화면의 %1$s 위젯을 사용하면 다음에 브라우저를 더욱 빠르게 시작하실 수 있습니다.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ 홈 화면에 위젯 추가하기
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ 홈 화면에 위젯이 추가됨
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-kw/strings.xml b/mobile/android/focus-android/app/src/main/res/values-kw/strings.xml
new file mode 100644
index 0000000000..7bfcb59c18
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-kw/strings.xml
@@ -0,0 +1,1097 @@
+
+
+
+
+
+
+
+
+ Hedhi
+
+ OK
+
+ Gwitha
+
+
+ Hwilas po entra trigva
+
+ Peuri privedh awtomatek.\nPeuri. Dilea. Daswul.
+
+
+ Dha istori peuri re beu diles.
+ Istori peuri diles
+
+
+ Diles re beu istori peuri an tab.
+
+
+ Hwilas %1$s
+
+
+ Kevrenna…
+
+
+ Derivas Kudyn an Wiasva
+
+
+ Ygeri yn %1$s
+
+
+ Ygeri yn…
+
+
+ Keworra dhe\'n skrin tre
+
+
+ Keworra dhe skochfordhow
+
+ Dilea a skochfordhow
+
+ Dewisyow
+ A-dro
+ Gweres
+ Dha Wiryow
+
+
+ Helerghellow lettys
+
+
+ Difudha hemma a yll martesen ewna nebes kudynnow an wiasva
+
+
+ Lettya Dalgh
+
+ Difudha dhe ewna nebes gwiasvaow
+
+
+ Nerthys gans %1$s
+
+
+ Kevrenna dre
+
+
+ Dilea istori peuri
+
+
+ Ygeri
+
+
+ Dilea hag Ygeri
+
+
+ Dilea
+
+
+ Dilea istori peuri
+
+
+
+ Dilea & ygeri
+
+
+ Dilea hag ygeri %1$s
+
+
+
+ Hwilas yn Focus
+
+ Hwilas yn Klar
+
+ Hwilas yn Focus Beta
+
+ Hwilas yn Focus Nightly
+
+
+ Gans %1$s yth esos\'ta ow kul maystri.
+Gwra hy usya avel peurell brivedh:
+
+ Hwilas ha peuri a-berth y\'n dowlen
+ Lettya helerghellow (po nowedhi settyansow dhe amyttya helerghellow)
+ Dilea pastiow, keffrys hag istori hwilas ha peuri
+
+
+Askorrys gans Mozilla yw %1$s. Agan amkan yw maga kesrosweyth yagh hag ygor.
+Dyski moy
]]>
+
+
+ Privetter & Diogeledh
+
+
+ Helerghi, pastiow, dewisyow kedhlow
+
+
+ Settya skwir, eskowlwul
+
+
+
+
+ A-dro dhe %1$s, gweres
+
+
+ Difresyans Gwellhes Rag Helerghi
+
+
+ Dalgh an Wi
+
+
+ Skwychya Appys
+
+
+ Ollgemmyn
+
+
+ Peurell, yeth skwir
+
+
+ Kuntel Kedhlow & Devnydh
+
+ Hwilas
+
+
+ Kavos profyansow hwilas
+
+ %1$s a dhanvon an pyth a skrifydh y\'n barr trigva dhe\'th jynn hwilas
+
+
+ Skwir
+
+
+ Jynn hwilas
+
+
+ Byw
+
+
+ Marow
+
+
+ Eskowlwul DAK
+
+
+ Rag Gwiasvaow drudh
+
+
+ Gweythresa dhe wul dhe %s kollenwel yn awtomatek moy es 450 URL gerys da y\'n barr trigva.
+
+
+ Rag gwiasvaow a Geworrydh
+
+
+ Gweythresa dhe wul dhe %s eskollenwel dha DAKow drudh.
+
+
+ Menystra gwiasvaow
+
+
+ Menystra gwiasvaow
+
+
+ + Keworra URL a-vusur
+
+
+ Agas rol eskollenwel:
+
+
+ Keworra DAK
+
+
+ Keworra DAK a-vusur
+
+
+ Keworra DAK a-vusur
+
+
+ Keworra kevren dhe eskowlwul
+
+
+ Pastiow ha Kedhlow Gwiva
+
+
+ Dewisyow Kedhlow
+
+
+ Dilea DAKow a-vusur
+
+
+ Dyski moy
+
+
+ Keworra ha menystra DAKow eskollenwel a-vusur.
+
+
+ DAK dhe geworra
+
+
+ Glusa po gorra DAK
+
+
+ Ensampel: mozilla.org
+
+
+ Ensampel: ensampel.com
+
+
+ DAK a-vusur nowydh keworrys.
+
+
+ Dilea
+
+
+ Dilea
+
+
+ Dascheck an DAK a wruss\'ta gorra..
+
+ Yeth
+
+ Skwir an kevreyth
+
+ Privetter
+ Lettya helerghellow argemynnow
+ Nebes argemynnow a helergh vysytyansow gwiasvaow, hwath mar ny glyckydh an argemynnow
+ Lettya helerghellow dielvenna
+ Usys dhe guntel, dielvenna, ha musura gwriansow kepar ha tappya ha rolya
+ Lettya helerghellow sosyel
+ Goworrys yn gwiasvaow dhe helerghi dha vysytyansow ha displetya nasweyth kepar ha botonys kevrenna
+ Lettya helerghellow dalgh erel
+ Gallosegi a yll lettya nebes folennow rag omdhegi yn hwaytyadow
+ Lettya pastiow
+
+
+ Na, meur ras
+ Lettya pastiow helerghi 3a-rannbarth yn unnik
+ Pastiow 3a-rannbarth yn unnik
+
+ Lettya pastiow treuswiva
+ Ya, mar pleg
+
+
+ Usya ol bys dhe dhialhwedha app
+
+
+ Dialhwedha gans ol bys mar kwruss\'ta keworra Skochfordhow po pan vo gwiasva ygor seulabrys yn %s.
+
+
+ Danngel
+
+ Kudha gwiasvaow hag ow skwychya appys ha lettya tenna skrinskeusennow.
+
+ Diogeledh
+
+ Performans
+ Lettya pryntolow an wi
+
+ Y hyll sewya dhe avenyow po skeusennow a fyll
+
+ Lettya JavaScript
+
+ Folennow a yll karga yn uskissa, mes ynwedh omdhegi yn anwaytyadow
+
+
+ Gul dhe %1$s bos peurell skwir
+
+ Mozilla
+ Danvon kedhlow devnydh
+
+
+ Dyski moy
+
+
+ Mozilla a veder orth kuntel an pyth yw res dhyn hepken dhe brovia ha gwellhe %1$s rag peub.
+
+
+ Deklaryans Privetter
+
+
+ Kedhlow leshyans
+
+
+ Kodguntellow a usyn
+
+
+ %s | Kodguntellow OSS
+
+
+ A-dro dhe %1$s
+
+
+ Jynnow-hwilas les
+
+
+ Dewis jynn hwilas
+
+
+ Daskavos jynnow hwilas skwir
+
+
+ + Keworra jynn hwilas aral
+ Dilea jynnow hwilas
+ Dilea
+
+
+ Keworra jynn hwilas
+
+ Dewis dha jynn drudh:
+
+
+ Keworra jynn hwilas
+
+ Hanow an jynn hwilas
+ Tokyn hwilas dhe usya
+ Gwitha
+
+
+ Ensampel: ensampel.com/search/?q=%s
+
+ Jynn hwilas nowydh keworrys.
+
+ Gorra hanow an jynn hwilas
+ Yma jynn hwilas les owth usya an hanow na seulabrys.
+
+ Gorra tokyn hwilas
+
+ Check bos an tokyn hwilas ow tesedha orth furvas Ensampel
+
+
+ Dilea ynworrans
+
+
+ Dyllo
+
+
+ Dilea istori peuri
+
+
+ Tabys ygor: %1$s
+
+
+ Junyans diogel
+
+
+ Ow karga
+
+
+ Gwiasva gergys
+
+
+ Dewisyow moy
+
+
+ Boton dewisyow moy
+
+
+ Gwaya war-rag
+
+
+ Daskarga wiasva
+
+
+ Gwaya war-gamm
+
+
+ Hedhi karga wiasva
+
+
+ Dehweles dhe app kyns
+
+
+ Niver a helerghellow lettys
+
+
+ Lettya helerghellow
+
+ Dha Wiryow
+
+ Ygeri kevren yn app aral
+
+ Ty a yll diberth a %1$s dhe ygeri an gevren ma yn %2$s.
+
+ Kavos app a yll ygeri an gevren ma
+
+ Nyns eus app vyth yn dha dhevis a yll ygeri an gevren ma. Ty a yll diberth a %1$s dhe hwilas app a yll y wul yn %2$s.
+
+ Kwytya peuri privedh?
+
+
+ %1$s gorfennys
+
+
+ Ygeri
+
+
+
+
+
+
+
+
+
+
+ Keworrys dhe skochfordhow!
+
+ Ny veu servyer kevys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Degea
+
+
+
+ Dynnargh dhe %1$s
+
+
+ Uskis. Privedh. Distennansow vyth.
+
+
+ Deus ha dalleth
+
+
+
+ Nyns yw %1$s kepar ha peurellow erel
+
+
+ Ni a wra kartha dha istori hag ow tegea an app a-barth privetter.
+
+
+
+ Gwra dhe %1$s bos dha beurell skwir dhe dhifres dha dhata gans pub kevren ygerys.
+
+
+ Settya dhe beurell skwir
+
+
+ Hepkor
+
+
+ Kennerth dha brivetter
+
+ Herdhya peuri privedh dres an kemmyn. Lettya argemynnow ha dalgh aral a yll dha holya dres gwivaow ha poshe prys karga folennow.
+
+
+ Dha hwilas, dha fordh
+
+ Hwilas neppyth aral? Dewis jynn hwilas skwir aral yn Dewisyow.
+
+
+ Keworra skochfordhow dhe\'th pennskrin
+
+ Dehweles dhe\'th re drudh yn %1$s yn uskis. Dewis \"Keworra dhe Bennskrin\" a\'n rol %1$s.
+
+
+ Gwra privetter dhe us
+
+ Settya %1$s dhe beurell skwir dhe gavos an prowyow a beuri privedh pan ygerowgh gwiasvaow a appys erel.
+
+ DL, my a wel!
+ Hepkor
+ Nessa
+
+
+ -
+
+
+ Keworra
+
+
+ YA
+
+
+ Hedhi
+
+
+ NA
+
+
+ Skochfordh a wra ygeri gans Difresyans Gwellhes rag Helerghi disweythresys
+
+
+ Esedhek peuri privedh
+
+
+ Gwarnyansow a\'th as dhe dhilea dha esedhek %1$s dre dapp. Nyns yw res dhis ygeri an app po gweles pyth usi ow resek y\'th peurell.
+
+
+ Dilea istori peuri
+
+
+ Iskarga Firefox
+
+
+
+
+
+
+
+
+ Leshyans Poblek Mozilla ha leshyansow fenten-ygor erel.]]>
+
+
+ omma.]]>
+
+
+ leshyans rydh ha fenten-ygor.]]>
+
+
+ Leshyans Poblek Ollgemmyn GNU v3 ha kavadow omma .]]>
+
+
+ Hanow devnydhyer
+ Ger tremena
+ Dilea
+
+
+
+ Junyans Diogel
+ Junyans Andhiogel
+
+ Gwirhes gans: %1$s
+
+
+ Diogeledh an wiasva
+ Yma DAK seulabrys
+
+
+ Hwilas y\'n Folen
+
+
+ Kavos y\'n folen
+
+
+ %1$d/%2$d
+
+ %1$d a somm %2$d
+
+
+ Kavos nessa sewyans
+
+ Kavos kynsa sewyans
+
+ Dyllo kavos y\'n folen
+
+
+
+
+ Pysi a wiva dhesken
+
+
+ Gwiva dhesken
+
+
+ URL dasskrifys
+
+
+ Toulys displegyer
+
+
+ Ygeri kevrennow yn appys
+
+
+ Avonsys
+
+
+ Kumyasow gwiva
+
+
+ Leheans Baneryow Pastiow
+
+
+ Byw
+
+
+ Marow
+
+
+ Leheans Baneryow Pastiow
+
+
+ Gweles le a vaneryow dre dhenagha govynnow pastiow yn awtomatek pan vo possybyl.
+
+ -->
+ Leheans Baneryow Pastiow
+
+
+ BYW y\'n wiasva ma
+
+
+ Nyns yw an wiasva skodhys y\'n eur ma
+
+
+ MAROW y\'n wiasva ma
+
+
+ Leheans Baneryow Pastiow
+
+
+ MAROW y\'n wiasva ma
+
+
+ BYW y\'n wiasva ma
+
+
+ Enowi Leheans Baneryow Pastiow rag %1$s?
+
+
+ Difudha Leheans Baneryow Pastow rag %1$s?
+
+
+ %1$s a wra kartha pastiow an wiasva ma ha daskarga an folen. Possybyl yw kartha pastiow dh\'agas digelmi po gwakhe karigellow prenassa.
+
+
+ %1$s a yll assaya denagha govynnow pastiow yn awtomatek.
+
+
+ Nyns yw skodhys an wiasva ma gans Leheans Baneryow Pastiow y\'n eur ma. A vynnsowgh ysi agan para a dhasweles an wiasva ma ha keworra skodhyans y\'n termyn a dheu?
+
+
+ Hedhi
+
+
+ Pysi a skodhyans
+
+
+ Govyn skodhya gwiasva derivys.
+
+
+ Govyn skodhya gwiasva derivys.
+
+
+
+ %1$s a assay denagha govynnow pastiow dhe woheles baneryow pastiow anius.\n\nRestra dewisyow baneryow pastiow yn %2$s.
+
+ dewisyow
+
+
+ Esseni
+
+
+ Dh\'y amyttya:
+
+
+ 1. Ke dhe Dewisyow Android
+
+
+ Kumyasow]]>
+
+
+ Ke dhe Dewisyow
+
+
+ %1$s dhe BYW]]>
+
+
+ Kamera
+
+
+ Korrgowsell
+
+
+ Tyller
+
+
+ Gwarnyans
+
+
+ Dalgh maystrys gans DRM
+
+
+ Pysi a amyttya
+
+
+ Lettys
+
+
+ Amyttys
+
+
+ Lettys gans Android
+
+
+ Amyttya sonadow ha gwydhyow
+
+
+ Lettya sonadow hepken
+
+
+ Profys
+
+
+ Lettya sonadow ha gwydhyow
+
+
+ Studhyansow
+
+
+ Possybyl yw y hwra lea ha gweythresa Firefox studhyansow a dermyn dhe dermyn.
+
+
+ Dyski moy
+
+
+ An gweythyn a wra degea dhe weytha chanjyow
+
+
+ Dilea
+
+
+ Gweythresek
+
+
+ Kowlwrys
+
+
+ Dispryvya a-bell dre USB/Wi-Fi
+
+
+ Dialhwedha
+
+
+ Afydhya Dre\'th Vysol
+
+
+ Ty a yll usya dha vysol dhe besya dha esedhek app a-lemmyn.
+
+
+ Ygeri Kevren yn Esedhek Nowydh
+
+
+ Aven bysol
+
+
+ Ny veu aswonnys an bysol. Assay arta.
+
+
+ Bys gwayys re uskis. Assay arta.
+
+
+ Diskwedhes profyansow hwilas?
+
+
+ Dhe gavos profyansow %1$s a wra danvon an pyth a skrifydh y\'n barr trigva dhe\'th jynn hwilas.
+
+
+ Na
+
+
+ Ya
+
+
+ Ny yll nebes jynnow hwilas diskwedhes profyansow hwilas.
+
+
+ Dyllo
+
+
+
+
+ Gwiva owth omdhegi yn anwaytyadow?\n Assay disweythresa Difresyans Helerghi
+
+
+ Keworra dhe Bennskrin]]>
+
+
+ Ygeri pub kevren yn %1$s\n Sett %1$s avel peurell skwir
+
+
+ Eskowlwul DAK rag gwivaow a usydh yn fenowgh %1$s Hirwask py DAK pynag y\'n barr trigva
+
+
+ Ygeri kevren yn tab nowydh\nHirwask py kevren bynag yn folen
+
+
+ Difudha hyntys y\'n skrin dalleth
+
+
+ Tab nowydh ygerys
+
+
+ Skwychya
+
+
+ Gweythresa modh skrin leun
+
+
+ Skwychya dhe gevren yn tab nowydh a-dhesempis
+
+
+ Lettya gwivaow a yll bos peryllus ha tollus
+
+ Lettya gwivaow gans derivas a vos tollus po omsettyans, gwivaow drogweyth, ha gwivaow a vedhelweyth anvodhek.
+
+
+ Modh HTTPS-Hepken
+
+ Yth assay yn awtomatek omjunya dhe wivaow der an arskwir argelans HTTPS rag diogeledh ynkressys.
+
+
+ Nammow
+
+ Ty a dhisweythresas lettya dalgh y\'n gwiasvaow ma.
+
+ Dilea
+
+ Dilea pub gwiasva
+
+
+ Lettya Pastiow
+
+
+ A vynn\'ta lettya pastiow?
+
+
+ Tab Diskerys
+
+ Diharesow. Yma kudyn genen ow karga an tab ma.
+
+ Avel peurell brivedh, ny withyn na gallos daskavos an tab ma.
+
+ Degea Tab
+
+
+
+
+
+ Danvon derivas diskara dhe Mozilla
+
+
+
+
+ Helerghellow lettys a-dhia %s
+
+ Dalgh
+
+ Argemynna
+
+ Sosyel
+
+ Re Dielvennel
+
+ Difresyans Gwellhes rag Helerghi
+
+ Difresyans yw MAROW y\'n wiva ma
+
+ Difresyans yw BYW y\'n wiva ma
+
+ Junyans yw diogel
+
+ Nyns yw junyans diogel
+
+ Helerghellow ha Skrifedhow dhe Lettya
+
+
+ Mos war-gamm
+
+
+
+ Dilea
+
+ Dashenwel
+
+ Dashenwel
+
+ Hanow skochfordh
+
+
+ <b>Ny vydh diles</b> imajys gwithys ha kevrennys pan dhilei istori %1$s
+
+
+
+ Thema
+
+ Golow
+
+ Tewl
+
+ Settys gans Gwither Batri
+
+ Sewya thema devis
+
+
+ Ny skoodh an wiasva ma HTTPS
+
+
+ Dyski moy
+ Chanjya an dewis ma yn Dewisyow >Privetter & Diogeledh > Diogeledh.]]>
+
+
+ Junyans andhiogel
+
+
+
+ Mar kwruss\'ta junya dhe\'n servyer ma yn sewen seulabrys, martesen yth yw an gwall anparghus.
+ ]]>
+
+
+ Martesen yma nebonan owth assaya omwul an wiasva ha peryllus via pesya.
+
+ Ny wra %1$s trestya dhe %2$s rag bos anwodhvos y dyller y destskrif, bos omsinys an testskrif, po nag usi an servyer ow tanvon an testskrifow kresel ewn.
+ ]]>
+
+
+
+ Degea tab
+
+
+
+ \'Mons genen! Ni a lettyas an wiasva ma rag dha aspia. Klyck an skoos p\'eurbynag dhe weles an pyth eson ow lettya.
+
+
+ Degea lammlen
+
+
+
+ Difresys os!
+
+ An dewisyow skwir ma a brof difresyans krev. Mes es yw godreylya an dewisyow dh\'aga dhesedha orth dha edhommow komparek.
+
+ Dyllo
+
+
+ Tapp omma dh\'y dewlel oll y\'n atal — istori, pastiow, puptra — ha dalleth gans tabb yr nowydh.
+
+
+
+
+ Degea
+
+
+ Hwilas widget
+
+
+ Istori peuri diles! 🎉
+
+
+ Dalleth dha esedhek peuri privedh hag y fydhyn ow lettya helerghellow ha taklow drog erel y\'n fordh.
+
+
+ Ni a\'th as dhe\'th peuri privedh, mes ty a yll dalleth uskissa nessa tro gans widget %1$s y\'th skrin tre.
+
+
+ Keworra widget dhe skrin tre
+
+
+ Widget keworrys dhe skrin tre
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-lo/strings.xml b/mobile/android/focus-android/app/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000000..787b72b4da
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-lo/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ ຍົກເລີກ
+
+ ຕົກລົງ
+
+ ບັນທຶກ
+
+
+ ຄົ້ນຫາ ຫລື ປ້ອນທີ່ຢູ່ໃສ່ນີ້
+
+ ການທ່ອງເວັບສ່ວນຕົວແບບອັດຕະໂນມັດ.\nທ່ອງເວັບ ແລະ ລົບລ້າງປະຫວັດການໃຊ້ງານອອກ.
+
+
+ ປະຫວັດການທ່ອງເວັບຂອງທ່ານໄດ້ຖືກລຶບອອກແລ້ວ.
+ ລຶບລ້າງປະຫວັດການທ່ອງເວັບແລ້ວ
+
+
+ ແທັບຂອງປະຫວັດການທ່ອງເວັບໄດ້ຖືກລຶບອອກແລ້ວ.
+
+
+ ຄົ້ນຫາສຳລັບ %1$s
+
+
+ ແບ່ງປັນ…
+
+
+ ລາຍງານບັນຫາໄຊ
+
+
+ ເປີດໃນ %1$s
+
+
+ ເປີດໃນ…
+
+
+ ເພີ່ມເຂົ້າໄປຫນ້າຈໍຫລັກ
+
+
+ ເພີ່ມທາງລັດ
+
+ ລຶບອອກຈາກທາງລັດ
+
+
+ ການຕັ້ງຄ່າ
+ ກ່ຽວກັບ
+ ວິທີໃຊ້
+ ສິດທິຂອງທ່ານ
+
+
+ ຕົວຕິດຕາມໄດ້ຖືກບັອກ
+
+
+ ການປິດສິງນີ້ອາດຈະຊ່ວຍແກ້ໄຂບັນຫາບາງຢ່າງຂອງເວັບໄຊທໄດ້
+
+
+ ການບັອກເນື້ອຫາ
+
+ ປິດເພື່ອແກ້ໄຂບາງບັນຫາຂອງເວັບໄຊທ
+
+
+ ຂັບເຄື່ອນໂດຍ %1$s
+
+
+ ແບ່ງປັນຜ່ານທາງ
+
+ ລຶບປະຫວັດການທ່ອງເວັບ
+ ແຕະ ຫຼື ລຶບການແຈ້ງເຕືອນນີ້ເພື່ອລຶບປະຫວັດການທ່ອງເວັບຂອງທ່ານຢ່າງປອດໄພ.
+
+
+ ແຕະ ຫຼື ປັດການແຈ້ງເຕືອນນີ້ເພື່ອລຶບປະຫວັດການທ່ອງເວັບຂອງທ່ານຢ່າງປອດໄພ.
+
+ ລຶບປະຫວັດການທ່ອງເວັບ
+
+
+ ເປີດ
+
+
+ ລຶບ ແລະ ເປີດ
+
+
+ ລຶບ
+
+
+ ລຶບປະຫວັດການທ່ອງເວັບ
+
+
+
+ ລຶບແລ້ວເປີດ
+
+
+ ລຶບ ແລະ ເປີດ %1$s
+
+
+
+ ຊອກຫາຢູ່ໃນຈຸດສຸມ
+
+ ຊອກຫາຢູ່ໃນ Klar
+
+ ຊອກຫາໃນ Focus Beta
+
+ ຊອກຫາໃນ Focus Nightly
+
+
+ %1$s ເຮັດໃຫ້ທ່ານສາມາດຄວບຄູມໄດ້.
+ໃຊ້ເປັນແບບບຣາວເຊີສ່ວນຕົວ:
+
+ ຄົ້ນຫາ ແລະ ທ່ອງເວັບພາຍໃນແອັບ
+ ບັອກຕົວຕິດຕາມ (ຫລື ອັບເດດການຕັ້ງຄ່າເພື່ອອະນຸຍາດຕົວຕິດຕາມ)
+ ລຶບຄຸກກີ້ລວມທັງປະຫວັດການຄົ້ນຫາ ແລະ ການທ່ອງເວັບອອກ
+
+
+%1$s ໄດ້ຮັບການສ້າງຂື້ນມາຈາກ Mozilla. ພາລະກິດຂອງພວກເຮົາແມ່ນສົ່ງເສີມການນຳໃຊ້ອິນເຕີເນັດທີ່ສົມບູນ ແລະ ເປີດກ້ວາງ
+ຮຽນຮູ້ເພີ່ມເຕີ່ມ
]]>
+
+
+ ຄວາມເປັນສ່ວນຕົວ ແລະ ຄວາມປອດໄພ
+
+
+ ການຕິດຕາມ, ຄຸກກີ້, ທາງເລືອກຂໍ້ມູນ
+
+
+ ຕັ້ງຄ່າເລີ່ມຕົ້ນ. ການຕື່ມຄຳອັດຕະໂນມັດ
+
+
+
+
+ ກ່ຽວກັບ %1$s, ຊ່ວຍເຫຼືອ
+
+
+ ເພີ່ມການປ້ອງກັນການຕິດຕາມ
+
+
+ ເນື້ອຫາເວັບ
+
+
+ ສະລັບແອັບ
+
+
+ ທົ່ວໄປ
+
+
+ ບຣາວເຊີຫລັກ, ພາສາ
+
+
+ ການເກັບຮວບຮວມ ແລະ ໃຊ້ຂໍ້ມູນ
+
+ ຊອກຫາ
+
+
+ ຮັບຂໍສະເໜີແນະການຄົ້ນຫາ
+
+ %1$s ຈະສົ່ງສິ່ງທີ່ທ່ານພີມໃນແຖບທີ່ຢູ່ໄປຍັງເຄື່ອງມືຄົ້ນຫາຂອງທ່ານ
+
+
+ ຄ່າພື້ນຖານ
+
+
+ ເຄື່ອງມືການຄົ້ນຫາ
+
+
+ ເປີດ
+
+
+ ປິດ
+
+
+ ການເຕີມ URL ແບບອັດຕະໂນມັດ
+
+
+ ສໍາລັບເວັບໄຊທຍອດນິຍົມ
+
+
+ ເປີດໃຊ້ງານເພື່ອໃຫ້ມີການເຕິມອັດຕະໂນມັດຂອງ %s ຜ່ານ 450 URLs ຍອດນິຍົມໃນແຖບທີ່ຢູ່
+
+
+ ສຳລັບເວັບໄຊທທີ່ທ່ານເພີ່ມ
+
+
+ ເປີດໃຊ້ເພື່ອໃຫ້ %s ຕື່ມຂໍ້ມູນ URLs ທີ່ທ່ານມັກໂດຍອັດຕະໂນມັດ.
+
+
+ ຄຸ້ມຄອງເວັບໄຊທ
+
+
+ ຄຸ້ມຄອງເວັບໄຊທ
+
+
+ + ເພີ່ມ URL ທີ່ກຳນົດເອງ
+
+
+ ລາຍຊື່ການຕື່ມຂໍ້ມູນອັດຕະໂນມັດຂອງທ່ານ:
+
+
+ ເພີ່ມ URL
+
+
+ ເພີ່ມ URL ທີ່ກຳນົດເອງ
+
+
+ ເພີ່ມ URL ທີ່ກຳນົດເອງ
+
+
+ ເພີ່ມລີ້ງເຂົ້າໄປ autocomplete
+
+
+ ຄຸກກີ້ ແລະ ຂໍ້ມູນໄຊ
+
+
+ ທາງເລືອກຂໍ້ມູນ
+
+
+ ເອົາ URLs ທີ່ກຳນົດເອງອອກ
+
+
+ ຮຽນຮູ້ເພີ່ມເຕີມ
+
+
+ ເພີ່ມ ແລະ ຈັດການ URLs ທີ່ເຕີມແບບອັດຕະໂນມັດທີ່ກຳໜົດເອງ.
+
+
+ URL ທີ່ຈະເພີ່ມ
+
+
+ ວາງ ຫລື ປ້ອນ URL
+
+
+ ຕົວຢ່າງ: mozilla.org
+
+
+ ຕົວຢ່າງ: example.com
+
+
+ ເພີ່ມ URL ທີ່ກຳນົດເອງໃຫມ່ແລ້ວ.
+
+
+ ລຶບ
+
+
+ ລຶບ
+
+
+ ກວດສອບ URL ທີ່ທ່ານໄດ້ປ້ອນເຂົ້າໄປ.
+
+ ພາສາ
+
+ ຄ່າເລີ່ມຕົ້ນຂອງລະບົບ
+
+ ຄວາມເປັນສ່ວນຕົວ
+ ບັອກຕົວຕິດຕາມຂອງການໂຄສະນາ
+ ມີໂຄສະນາບາງໂຕສາມາດຕິດຕາມການເຂົ້າໄປເວັບຕ່າງໆ ເຖິງແມ່ນວ່າທ່ານຈະບໍ່ໄດ້ຄິກໃສ່ໂຄສະນານັ້ນເລີຍ
+ ບັອກຕົວຕິດຕາມຂອງການວິເຄາະ
+ ໃຊ້ເພື່ອເກັບກຳຮວບຮ່ວມ, ວິເຄາະ ແລະ ວັດແທກກິດຈະກຳເຊັນວ່າການແຕະ ແລະ ການເລື່ອນ
+ ບັອກຕົວຕິດຕາມຂອງສືສັງຄົມອອນໄລນ໌
+ ຝັງໄວ້ຢູ່ໃນເວັບໄຊທ໌ເພື່ອຕິດຕາມເບິງການເຂົ້າມາເບິງຂອງທ່ານ ແລະ ເພື່ອສະແດງຟັງຊັນເຊັ່ນວ່າປຸ່ມແບ່ງປັນ
+ ບັອກຕົວຕິດຕາມຂອງເນື້ອຫາຕ່າງໆ
+ ການເປີດໃຊ້ງານອາດຈະເຮັດໃຫ້ບາງຫນ້າເຮັດວຽກຜຶດປົກກະຕິ
+ ລະງັບຄຸກກີ້
+
+
+ ບໍ່, ຂອບໃຈ
+ ບັອກຕົວຕິດຕາມຄຸກກີ້ຈາກບຸກຄົນທີ່ສາມເທົ່ານັ້ນ
+ ລະງັບຄຸກກີ້ພາຍນອກເທົ່ານັ້ນ
+
+ ບລັອກຄຸກກີຂ້າມເວັບໄຊ
+ ໂດຍ, ແມ່ນແລ້ວ
+
+
+ ນຳໃຊ້ລາຍນິ້ວມືເພື່ອປົດລ໋ອກແອັບ
+
+
+ ປົດລັອກໂດຍໃຊ້ລາຍນິ້ວມືຖ້າທ່ານໄດ້ເພີ່ມທາງລັດ ຫຼືເມື່ອເວັບໄຊທ໌ເປີດຢູ່ໃນ %s ແລ້ວ.
+
+
+ ອຳພາງຕົວຕົນ
+
+ ເຊື່ອງຫນ້າເວັບເມື່ອສະລັບແອັບ ແລະ ລະງັບການຖ່າຍຮູບໜ້າຈໍ.
+
+ ຄວາມປອດໄພ
+
+ ປະສິດທິພາບ
+ ບັອກຕົວອັກສອນເວັບ
+
+ ອາດຈະສົ່ງຜົນເຮັດໃຫ້ໄອຄອນ ຫລື ຮູບພາບຂາດຫາຍໄປ
+
+ ລະງັບ JavaScript
+
+ ໜ້າອາດໂຫຼດໄວຂື້ນ ແຕ່ອາດເຮັດວຽກຢ່າງບໍ່ຄາດຄິດ
+
+
+ ຕັ້ງ %1$s ໃຫ້ເປັນບຣາວເຊີຫລັກ
+
+ Mozilla
+ ສົ່ງຂໍ້ມູນການນຳໃຊ້
+
+
+ ຮຽນຮູ້ເພີ່ມເຕີມ
+
+
+ Mozilla ພະຍາຍາມທີ່ຈະເກັບຮວບຮ່ວມສະເພາະສິ່ງທີ່ຈຳເປັນຕໍ່ການປັບປູງ %1$s ສຳລັບທຸກໆຄົນ.
+
+
+ ນະໂຍບາຍຄວາມເປັນສ່ວນຕົວ
+
+
+ ຂໍ້ມູນກ່ຽວກັບລາຍເຊັນ
+
+
+ ໄລແບຣລີທີ່ພວກເຮົານຳໃຊ້
+
+
+ %s | ໄລບຣາລີ OSS
+
+
+ ກ່ຽວກັບ %1$s
+
+
+ ໄດ້ທຳການຕິດຕັ້ງເຄື່ອງມືການຄົ້ນຫາແລ້ວ
+
+
+ ເລືອກເຄື່ອງມືການຄົ້ນຫາ
+
+
+ ກູ້ຄືນເຄື່ອງມືພື້ນຖານຂອງການຄົ້ນຫາ
+
+
+ + ເພີ່ມລະບົບການຊອກຫາຕົວອື່ນ
+ ລຶບເຄື່ອງມືການຄົ້ນຫາ
+ ລຶບ
+
+
+ + ເພີ່ມລະບົບການຊອກຫາຕົວອື່ນ
+
+ ເລືອກລະບົບທີ່ທ່ານຕ້ອງການ:
+
+
+ ເພີ່ມເຄື່ອງມືການຄົ້ນຫາ
+
+ ຊື່ເຄື່ອງມືການຄົ້ນຫາ
+ ສະຕຣິງການຊອກຫາທີ່ຈະໃຊ້
+ ບັນທຶກ
+
+
+ ຕົວຢ່າງ: example.com/search/?q=%s
+
+ ໄດ້ເພີ່ມເຄືອງມີການຊອກຫາຕົວໃຫມ່ແລ້ວ
+
+ ປ້ອນຊື່ເຄື່ອງມືການຊອກຫາ
+ ເຄື່ອງມືຄົ້ນຫາທີ່ຕິດຕັ້ງໄວ້ກຳລັງໃຊ້ຊື່ນັ້ນຢູ່ແລ້ວ.
+
+ ປ້ອນສະຕຣິງການຊອກຫາ
+
+ ກວດສອບວ່າສະຕິງການຄົ້ນຫາກົງກັບຮູບແບບຕົວຢ່າງ
+
+
+ ລົບລ້າງຂໍ້ມູນທີ່ປ້ອນເຂົ້າ
+
+
+ ຍົກເລີກ
+
+
+ ລຶບປະຫວັດການທ່ອງເວັບ
+
+
+ ແທັບທີ່ເປີດ: %1$s
+
+
+ ການເຊື່ອມຕໍ່ທີ່ປອດໄພ
+
+
+ ກຳລັງໂຫລດ
+
+
+ ໂຫລດເວັບໄຊທ໌ນີ້ແລ້ວ
+
+
+ ຕົວເລືອກເພີ່ມເຕີມ
+
+
+ ປຸ່ມຕົວເລືອກເພີ່ມເຕີມ
+
+
+ ນຳທາງໄປທາງຫນ້າ
+
+
+ ໂຫລດເວັບໄຊທ໌ຄືນໃຫມ່
+
+
+ ນຳທາງຍ້ອນກັບ
+
+
+ ຢຸດການໂຫລດເວັບໄຊທ໌
+
+
+ ກັບໄປຫາແອັບກ່ອນຫນ້ານີ້
+
+
+ ຈຳນວນຕົວຕິດຕາມທີ່ຖືກລະງັບ
+
+
+ ບັອກຕົວຕິດຕາມ
+
+ ສິດທິຂອງທ່ານ
+
+ ເປີດລີ້ງນີ້ໃນແອັບອື່ນ
+
+ ທ່ານສາມາດອອກຈາກ %1$s ເພື່ອເປີດລີ້ງນີ້ໃນ %2$s.
+
+ ຊອກຫາແອັບທີ່ສາມາດເປີດລີ້ງນີ້
+
+ ບໍ່ມີແອັບໃດເລີຍໃນອຸປະກອນຂອງທ່ານທີ່ຈະສາມາດເປີດລີ້ງນີ້ໄດ້. ທ່ານສາມາດອອກຈາກ %1$s ເພື່ອຄົ້ນຫາ %2$s ແອັບທີ່ສາມາດເປີດລີ້ງນີ້ໄດ້.
+
+ ຕ້ອງການອອກຈາກການທອງເວັບແບບສ່ວນຕົວບໍ່?
+
+
+ ສຳເລັດແລ້ວ %1$s
+
+
+ ເປີດ
+
+
+
+
+
+
+
+
+
+
+ ເພີ່ມໃສ່ທາງລັດແລ້ວ!
+
+ ບໍ່ພົບເຊີເວີ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ປິດ
+
+
+
+ ຍິນດີຕ້ອນຮັບສູ່ %1$s
+
+
+ ໄວ. ສ່ວນຕົວ. ບໍ່ມີສິ່ງລົບກວນ.
+
+
+ ເລີ່ມຕົ້ນໃຊ້ງານ
+
+
+
+ %1$s ບໍ່ຄືກັບຕົວທ່ອງເວັບອື່ນໆ
+
+
+ ພວກເຮົາລຶບລ້າງປະຫວັດຂອງທ່ານເມື່ອທ່ານປິດແອັບເພື່ອຄວາມເປັນສ່ວນຕົວເພີ່ມເຕີມ.
+
+
+
+ ເຮັດໃຫ້ %1$s ເປັນຄ່າເລີ່ມຕົ້ນຂອງທ່ານເພື່ອປົກປ້ອງຂໍ້ມູນຂອງທ່ານດ້ວຍທຸກລິ້ງທີ່ທ່ານເປີດ.
+
+
+ ຕັ້ງເປັນຕົວທ່ອງເວັບເລີ່ມຕົ້ນ
+
+
+ ຂ້າມ
+
+
+
+ ເພີ່ມຄວາມເປັນສ່ວນໂຕຂອງທ່ານ
+
+ ນຳການທ່ອງເວັບແບບສ່ວນຕົວໄປສູ້ລະດັບຖັດໄປ. ປິດກັ້ນໂຄສະນາແລະເນື້ອຫາອື່ນໆ ທີ່ສາມາດຕິດຕາມທ່ານລະຫວ່າງເວັບໄຊທ໌ແລະຫລຸດເວລາໃນການໂຫລດຫນ້າ,
+
+
+ ການຄົ້ນຫາໃນແບບຂອງທ່ານເອງ
+
+ ກຳລັງຊອກຫາບາງສິ່ງບາງຢ່າງທີ່ແຕກຕ່າງບໍ່? ເລືອກເອົາຕົວຄົ້ນຫາພື້ນຖານອື່ນໆໃນການຕັ້ງຄ່າ.
+
+
+ ເພີ່ມທາງລັດໃນໜ້າຈໍຫຼັກຂອງທ່ານ
+
+ ກັບໄປທີ່ໄຊທີ່ທ່ານມັກໃນ %1$s ໄດ້ຢ່າງວ່ອງໄວ. ພຽງເລືອກ \"ເພີ່ມໄປໜ້າຈໍຫຼັກ\" ຈາກ %1$s ເມນູ.
+
+
+ ສ້າງນິໄສຄວາມເປັນສ່ວນຕົວ
+
+ ຕັ້ງ %1$s ເປັນບຣາວເຊີຫລັກຂອງທ່ານ ແລະ ຮັບປະໂຫຍດຈາກການທ່ອງເວັບແບບສ່ວນຕົວເມື່ອທ່ານເປີດຫນ້າເວັບຈາກແອັບອື່ນໆ.
+
+ ຕົກລົງ, ເຂົ້າໃຈແລ້ວ!
+ ຂ້າມໄປ
+ ຕໍ່ໄປ
+
+
+ -
+
+
+ ເພີ່ມ
+
+
+ ແມ່ນແລ້ວ
+
+
+ ຍົກເລີກ
+
+
+ ບໍ່
+
+
+ ທາງລັດຈະເປີດພ້ອມປິດການໃຊ້ງານການປ້ອງກັນການຕິດຕາມ
+
+
+ "ເຊສຊັນການທ່ອງເວັບແບບສ່ວນຕົວ "
+
+
+ ການແຈ້ງເຕືອນຊ່ວຍໃຫ້ທ່ານລ້າງວາລະ %1$s ຂອງທ່ານດ້ວຍການແຕະ. ທ່ານບໍ່ຈຳເປັນຕ້ອງເປີດແອັບ ຫຼື ເບິ່ງສິ່ງທີ່ກຳລັງເຮັດວຽກໃນບາວເຊີຂອງທ່ານ.
+
+
+ ລຶບປະຫວັດການທ່ອງເວັບ
+
+
+ ດາວໂຫລດ Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License ແລະ ສັນຍາອະນຸຍາດໂອເພ່ນຊອດອື່ນໆ.]]>
+
+
+ ນີ້.]]>
+
+
+ ສັນຍາອະນຸຍາດເສລີ ແລະ ໂອເພ່ນຊອດອື່ນໆ.]]>
+
+
+ GNU General Public License v3, ແລະ ໃຊ້ໄດ້ນີ້ .]]>
+
+
+ ຊື່ຜູ້ໃຊ້
+ ລະຫັດຜ່ານ
+ ລ້າງ
+
+
+
+ ການເຊື່ອມຕໍ່ທີປອດໄພ
+ ການເຊື່ອມຕໍ່ທີບໍ່ປອດໄພ
+
+ ກວດສອບໂດຍ: %1$s
+
+
+ ຄວາມປອດໄພຂອງເວັບໄຊທ໌
+ URL ນີ້ມີຢູ່ແລ້ວ
+
+
+ ຄົ້ນຫາໃນເພຈ
+
+
+ ຄົ້ນຫາໃນເພຈ
+
+
+ %1$d/%2$d
+
+ %1$d ຈາກ %2$d
+
+
+ ຄົ້ນຫາຜົນລັບຕໍ່ໄປ
+
+ ຄົ້ນຫາຜົນລັບກ່ອນໜ້ານີ້
+
+ ຍົກເລີກການຄົ້ນຫາໃນໜ້າ
+
+
+
+
+ ຂໍໄຊສຳລັບເດສທອບ
+
+
+ ໂຫມດເດັກສທັອບ
+
+
+ URL ສຳເນົາແລ້ວ
+
+
+ ເຄື່ອງມືນັກພັດທະນາ
+
+
+ ເປີດລີ້ງໃນແອັບ
+
+
+ ຂັ້ນສູງ
+
+
+ ສິດທິໃນເວັບໄຊທ
+
+
+ ການຫຼຸດປ້າຍໂຄສະນາຄຸກກີ
+
+
+ ເປີດ
+
+
+ ປິດ
+
+
+ ການຫຼຸດປ້າຍໂຄສະນາຄຸກກີ
+
+
+ ເບິ່ງປ້າຍໂຄສະນາຫນ້ອຍລົງໂດຍການປະຕິເສດຄໍາຮ້ອງຂໍຄຸກກີໂດຍອັດຕະໂນມັດ, ເມື່ອເປັນໄປໄດ້.
+
+ -->
+ ການຫຼຸດປ້າຍໂຄສະນາຄຸກກີ
+
+
+ ເປີດສໍາລັບເວັບໄຊທ໌ນີ້
+
+
+ ຕອນນີ້ບໍ່ຮອງຮັບເວັບໄຊ
+
+
+ ປິດສຳລັບເວັບໄຊນີ້
+
+
+ ການຫຼຸດປ້າຍໂຄສະນາຄຸກກີ
+
+
+ ປິດສຳລັບເວັບໄຊນີ້
+
+
+ ເປີດສໍາລັບເວັບໄຊທ໌ນີ້
+
+
+ ເປີດໃຊ້ການຫຼຸດປ້າຍໂຄສະນາຄຸກກີສຳລັບ %1$s ບໍ?
+
+
+ ປິດການຫຼຸດປ້າຍໂຄສະນາຄຸກກີສຳລັບ %1$s ບໍ?
+
+
+ %1$s ຈະລຶບລ້າງຄຸກກີ້ຂອງເວັບໄຊນີ້ ແລະໂຫຼດໜ້ານີ້ຄືນໃໝ່. ການລຶບລ້າງຄຸກກີ້ທັງໝົດອາດເຮັດໃຫ້ເຈົ້າອອກຈາກລະບົບ ຫຼືກະຕ່າຊື້ເຄື່ອງຫວ່າງເປົ່າ.
+
+
+ %1$s ສາມາດພະຍາຍາມປະຕິເສດການຮ້ອງຂໍຄຸກກີໂດຍອັດຕະໂນມັດ.
+
+
+ ຕອນນີ້ເວັບໄຊນີ້ບໍ່ຮອງຮັບການຫຼຸດປ້າຍໂຄສະນາຄຸກກີ. ທ່ານຕ້ອງການຂໍໃຫ້ທີມງານຂອງພວກເຮົາທົບທວນຄືນເວັບໄຊທ໌ນີ້ແລະເພີ່ມການສະຫນັບສະຫນູນໃນອະນາຄົດບໍ?
+
+
+ ຍົກເລີກ
+
+
+ ຮ້ອງຂໍການຊ່ວຍເຫຼືອ
+
+
+ ຮ້ອງຂໍໃຫ້ສະຫນັບສະຫນູນເວັບໄຊທີ່ສົ່ງ.
+
+
+ ຮ້ອງຂໍໃຫ້ສະຫນັບສະຫນູນເວັບໄຊທີ່ສົ່ງ.
+
+
+
+ %1$s ພະຍາຍາມປະຕິເສດການຮ້ອງຂໍຄຸກກີເພື່ອປິດປ້າຍໂຄສະນາຄຸກກີທີ່ໜ້າລຳຄານ.\n\nຈັດການການຕັ້ງຄ່າປ້າຍໂຄສະນາຄຸກກີໃນ %2$s.
+
+
+ ການຕັ້ງຄ່າ
+
+
+ ຫຼິ້ນອັດຕະໂນມັດ
+
+
+ ເພື່ອຕ້ອງການອະນຸຍາດ:
+
+
+ 1. ໄປທີ່ການຕັ້ງຄ່າ Android
+
+
+ ສິດ]]>
+
+
+ ໄປທີ່ການຕັ້ງຄ່າ
+
+
+ %1$s ເປັນເປີດ]]>
+
+
+ ກ້ອງຖ່າຍຮູບ
+
+
+ ໄມໂຄຣໂຟນ
+
+
+ ຕຳແຫນ່ງທີ່ຕັ້ງ
+
+
+ ການແຈ້ງເຕືອນ
+
+
+ ເນື້ອຫາທີ່ຄວບຄຸມໂດຍ DRM
+
+
+ ຖາມເພື່ອອະນຸຍາດ
+
+
+ ບັອກ
+
+
+ ອະນຸຍາດ
+
+
+ ຖືກບລັອກໂດຍ Android
+
+
+ ອະນຸຍາດສຽງ ແລະ ວິດີໂອ
+
+
+ ບັອກສຽງຢ່າງດ່ຽວ
+
+
+ ແນະນຳ
+
+
+ ບັອກສຽງ ແລະ ວິດີໂອ
+
+
+ ການສຶກສາ
+
+
+ Firefox ອາດຈະຕິດຕັ້ງແລະດໍາເນີນການສຶກສາເປັນບາງເວລາ.
+
+
+ ຮຽນຮູ້ເພີ່ມເຕີມ
+
+
+ ແອັບພລິເຄຊັນຈະປິດເພື່ອນຳໃຊ້ການປ່ຽນແປງ
+
+
+ ລຶບ
+
+
+ ໃຊ້ງານຢູ່
+
+
+ ສຳເລັດແລ້ວ
+
+
+ ການດີບັກໄລຍະໄກຜ່ານ USB/Wi-Fi
+
+
+ ປົດລັອກ
+
+
+ ຢືນຢັນການໃຊ້ລາຍນິ້ວມືຂອງທ່ານ
+
+
+ ທ່ານສາມາດໃຊ້ລາຍນິ້ວມືຂອງທ່ານເພື່ອສືບຕໍ່ເຊດຊັນແອັບປັດຈຸບັນຂອງທ່ານໄດ້.
+
+
+ ເປີດລິ້ງໃນເຊດຊັນໃໝ່
+
+
+ ໄອຄອນລາຍນິ້ວມື
+
+
+ ບໍ່ຮູ້ຈັກລາຍນິ້ວມືນີ້. ລອງໃໝ່.
+
+
+ ຍັບນິ້ວໄວເກີນໄປ. ລອງອີກຄັ້ງ.
+
+
+ ສະແດງຄຳແນະນຳໃນການຄົ້ນຫາບໍ?
+
+
+ ເພື່ອຮັບເອົາຄຳແນະນຳ, %1$s ຕ້ອງການສົ່ງສິ່ງທີ່ທ່ານພິມໃນແຖບທີ່ຢູ່ໄປຫາເຄື່ອງມືຊອກຫາ.
+
+
+ ບໍ່
+
+
+ ຕົກລົງ
+
+
+ ເຄື່ອງມືຄົ້ນຫາບາງໂຕບໍ່ສາມາດສະແດງຂໍ້ສະເໜີແນະໄດ້.
+
+
+ ຍົກເລີກ
+
+
+
+
+ ໄຊເຮັດວຽກຢ່າງບໍ່ຄາດຄິດ?\n ລອງປິດການປົກປ້ອງການຕິດຕາມ
+
+
+ ເພີ່ມໄປສູ່ໜ້າຈໍຫຼັກ "]]>
+
+
+ ເປີດທຸກລີ້ງໃນ%1$s\n ຕັ້ງ %1$s ເປັນບ່າວເຊີເລີ່ມຕົ້ນ
+
+
+ ເຕີມ URLs ໂດຍອັດຕະໂນມັດສຳລັບເວັບໄຊທີ່ທ່ານໃຊ້ຫຼາຍທີ່ສຸດ\n ແຕະຄ້າງທີ່ URL ໃດໆ ໃນແທບທີ່ຢູ່
+
+
+ ເປີດລີ້ງໃນແທບໃໝ່\n ແຕະຄ້າງທີ່ລີ້ງໃດໆໃນໜ້າ
+
+
+ ປິດເຄັດລັບໃນໜ້າຈໍເລີ່ມຕົ້ນ
+
+
+ ເປີດແທັບໃຫມ່ແລ້ວ
+
+
+ ສະລັບ
+
+
+ ເຂົ້າສູ່ໂມດເຕັມຫນ້າຈໍ
+
+
+ ສະລັບໄປຫາແທັບໃຫມ່ທັນທີ
+
+
+ ບັອກເວັບໄຊທທີ່ເປັນອັນຕະລາຍ ແລະ ຕົ້ມຕຸນ
+
+ ບັອກເວັບໄຊທຕົ້ມຕຸນ ແລະ ຈູ່ໂຈມ, ເວັບໄຊທີ່ມີມອລແວ ແລະ ເວັບໄຊທທີ່ມີຊັອບແວທີ່ບໍ່ຕ້ອງການຕາມທີ່ໄດ້ຮັບການລາຍງານ.
+
+
+ ໂໝດ HTTPS ເທົ່ານັ້ນ
+
+
+ ພະຍາຍາມເຊື່ອມຕໍ່ຫາເວັບໄຊໂດຍອັດຕະໂນມັດໂດຍໃຊ້ໂປຣໂຕຄໍການເຂົ້າລະຫັດ HTTPS ເພື່ອຄວາມປອດໄພທີ່ເພີ່ມຂຶ້ນ.
+
+
+ ຍົກເວັ້ນ
+
+ ທ່ານໄດ້ປິດຕົວບັອກເນື້ອຫາສຳລັບເວັບໄຊທເຫລົ່ານີ້.
+
+ ລຶບ
+
+ ລຶບເວັບໄຊທທັງຫມົດ
+
+
+ ບລັອກຄຸກກີ້
+
+
+ ທ່ານຕ້ອງການບລັອກຄຸກກີບໍ?
+
+
+ ແທັບຂັດຂ້ອງ
+
+ ຂໍອະໄພ ພວກເຮົາກຳລັງບັນຫາກັບແທັບນີ້
+
+ ໃນເວັບບຣາວເຊີແບບສ່ວນຕົວ ພວກເຮົາບໍ່ໄດ້ບັນທຶກ ແລະ ບໍ່ສາມາດກູ້ຄືນແທັບນີ້ໄດ້.
+
+ ປິດແທັບ
+
+
+
+
+
+ ສົ່ງລາຍງານການຂັດຂ້ອງໄປຫາ Mozilla
+
+
+
+
+ ບລັອກຕົວຕິດຕາມຕັ້ງແຕ່ %s
+
+ ເນື້ອຫາ
+
+ ການໂຄສະນາ
+
+ ສັງຄົມ
+
+
+ ການວິເຄາະ
+
+
+ ເພີ່ມການປ້ອງກັນການຕິດຕາມ
+
+ ການປົກປ້ອງຖືກປິດໄວ້ສຳລັບເວັບໄຊນີ້
+
+ ການປົກປ້ອງແມ່ນເປີດຢູ່ສຳລັບເວັບໄຊນີ້
+
+ ການເຊື່ອມຕໍ່ແມ່ນປອດໄພ
+
+ ການເຊື່ອມຕໍ່ບໍ່ປອດໄພ
+
+
+ ຕົວຕິດຕາມແລະສະຄິບຈະຖືກບ໋ອກ
+
+
+ ກັບຄືນ
+
+
+
+ ລຶບ
+
+ ປ່ຽນຊື່
+
+ ປ່ຽນຊື່
+
+ ຊື່ທາງລັດ
+
+
+ <b>ຈະບໍ່ລຶບ</b>ພາບທີບັນທືກໄວ້ ແລະ ແບ່ງປັນເມືອທ່ານລ້າງປະຫວັດຂອງ %1$s
+
+
+
+ ຊຸດປັບແຕ່ງ
+
+ ແຈ້ງ
+
+ ມືດ
+
+
+ ຕັ້ງໂດຍຕົວປະຢັດແບດເຕີລີ່
+
+ ຕາມຊຸດຕົກແຕ່ງອຸປະກອນ
+
+
+ ເວັບໄຊນີ້ບໍ່ຮອງຮັບ HTTPS
+
+
+ ສຶກສາເພີ່ມເຕີມ
+ ປ່ຽນການຕັ້ງຄ່ານີ້ໃນການຕັ້ງຄ່າ > ຄວາມເປັນສ່ວນຕົວ & amp; ຄວາມປອດໄພ > ຄວາມປອດໄພ.]]>
+
+
+ ການເຊື່ອມຕໍ່ບໍ່ປອດໄພ
+
+
+
+ຖ້າຫາກວ່າທ່ານເຄີຍເຊື່ອມຕໍ່ກັບເຊີເວີນີ້ໄດ້ສຳເລັດມາກ່ອນ ຂໍ້ຜິດພາດນີ້ອາດຈະເກີດຂື້ນພຽງຊົ່ວຄາວ ແລະ ທ່ານສາມາດລອງເຂົ້າໃຫມ່ໃນພາຍຫລັງ.
+]]>
+
+
+ ມີບາງຄົນອາດຈະກຳລັງປອມແປງເວັບໄຊທ ແລະ ທ່ານບໍ່ຄວນຈະເຂົ້າໄປ.
+
+ ເວັບໄຊທຈະຢັ້ງຢືນຕົວຕົນຜ່ານທາງໃບຮັບຮອງ. %1$s ບໍ່ຫນ້າເຊື່ອຖື %2$s ເພາະວ່າຜູ້ອອກໃບຮັບຮອງຂອງພວກເຂົາແມ່ນບໍເປັນທີ່ຮູ້ຈັກ, ໃບຮັບຮອງແມ່ນສ້າງຂື້ນມາເອງ ຫລື ເຊີເວີບໍ່ໄດ້ສົ່ງໃບຮັບຮອງທີ່ຖືກຕ້ອງມາໃຫ້.
+ ]]>
+
+
+
+ ປິດແທັບ
+
+
+
+ ໄດ້ແລ້ວ! ພວກເຮົາໄດ້ຢຸດເຊົາເວັບໄຊນີ້ຈາກການລັກຕິດຕາມກ່ຽວກັບທ່ານ. ແຕະໃສ່ໄສ້ໄດ້ທຸກເວລາເພື່ອເບິ່ງສິ່ງທີ່ພວກເຮົາຂັດຂວາງ.
+
+
+ ປິດປັອບອັບ
+
+
+
+ ເຈົ້າໄດ້ຮັບການປົກປ້ອງ!
+
+ ການຕັ້ງຄ່າເລີ່ມຕົ້ນເຫຼົ່ານີ້ໃຫ້ການປົກປ້ອງທີ່ເຂັ້ມແຂງ. ແຕ່ມັນງ່າຍທີ່ຈະປັບການຕັ້ງຄ່າເພື່ອຕອບສະໜອງຄວາມຕ້ອງການສະເພາະຂອງເຈົ້າ.
+
+ ປິດ
+
+
+ ແຕະບ່ອນນີ້ເພື່ອຖິ້ມມັນທັງໝົດ — ປະຫວັດ, ຄຸກກີ້, ທຸກຢ່າງ — ແລະເລີ່ມໃໝ່ໃນແຖບໃໝ່.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ ປິດ
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ ຊອກຫາ widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ລຶບລ້າງປະຫວັດການທ່ອງເວັບແລ້ວ! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ເລີ່ມເຊດຊັນການທ່ອງເວັບສ່ວນຕົວຂອງເຈົ້າ, ແລະພວກເຮົາຈະບລັອກຕົວຕິດຕາມ ແລະສິ່ງບໍ່ດີອື່ນໆຕາມທີ່ເຈົ້າໄປ.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ພວກເຮົາຈະປ່ອຍໃຫ້ທ່ານຊອກຫາແບບສ່ວນຕົວຂອງທ່ານ, ແຕ່ເລີ່ມຕົ້ນໄວຂຶ້ນໃນຄັ້ງຕໍ່ໄປດ້ວຍວິດເຈັດ %1$s ໃນໜ້າຈໍຫຼັກຂອງທ່ານ.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ ເພີ່ມ widget ໃສ່ຫນ້າຈໍຫຼັກ
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ ເພີ່ມວິດເຈັດໃສ່ໜ້າຈໍຫຼັກແລ້ວ
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-lt/strings.xml b/mobile/android/focus-android/app/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000000..3a35412da4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-lt/strings.xml
@@ -0,0 +1,841 @@
+
+
+
+
+
+
+
+
+ Atsisakyti
+
+ Gerai
+
+ Įrašyti
+
+
+ Įveskite adresą arba paieškos žodžius
+
+ Automatinis privatusis naršymas.\nNaršykite. Išvalykite. Kartokite.
+
+
+ Jūsų naršymo žurnalas išvalytas.
+ Naršymo žurnalas išvalytas
+
+
+ Kortelės naršymo žurnalas išvalytas.
+
+
+ Ieškoti „%1$s“
+
+
+ Dalintis…
+
+
+ Pranešti apie svetainės problemą
+
+
+ Atverti naudojant „%1$s“
+
+
+ Atverti naudojant…
+
+
+ Įtraukti į pradžios ekraną
+
+
+ Pridėti prie leistukų
+
+ Pašalinti iš leistukų
+
+ Nustatymai
+ Apie
+ Žinynas
+ Jūsų teisės
+
+
+ Užblokuoti sekimo elementai
+
+
+ Išjungus gali būti išspręstos kai kurios svetainių problemos
+
+
+ Turinio blokavimas
+
+ Išjungus sutvarkysite kai kurias svetaines
+
+
+ Įgalino „%1$s“
+
+
+ Dalintis naudojant
+
+
+ Valyti naršymo žurnalą
+
+
+ Atverti
+
+
+ Išvalyti ir atverti
+
+
+ Valyti
+
+
+ Valyti naršymo žurnalą
+
+
+
+ Išvalyti ir atverti
+
+
+ Išvalyti ir atverti „%1$s“
+
+
+
+ „%1$s“ suteikia jums kontrolę.
+Naudokite ją kaip privačią naršyklę:
+
+ Ieškokite ir naršykite pačioje programoje
+ Blokuokite stebėjimo elementus (arba pakeiskite nustatymus, jeigu norite juos leisti)
+ Išvalykite, norėdami pašalinti slapukus, paieškos ir naršymo žurnalus
+
+
+„%1$s“ yra „Mozillos“ kūrinys. Mūsų misija yra skatinti sveiką, atvirą internetą.
+Sužinoti daugiau
]]>
+
+
+ Privatumas ir saugumas
+
+
+ Stebėjimas, slapukai, duomenų siuntimas
+
+
+ Parinkite numatytąją, automatinio užbaigimo
+
+
+
+
+ Apie „%1$s“, žinynas
+
+
+ Išplėsta apsauga nuo stebėjimo
+
+
+ Saityno turinys
+
+
+ Programų perjungimas
+
+
+ Bendrieji
+
+
+ Numatytoji naršyklė, kalba
+
+
+ Duomenų rinkimas ir naudojimas
+
+ Paieška
+
+
+ Gauti paieškos žodžių siūlymus
+
+ „%1$s“ perduos jūsų ieškyklei tai, ką rašote adreso lauke
+
+
+ Numatytoji
+
+
+ Ieškyklė
+
+
+ Taip
+
+
+ Ne
+
+
+ Automatinis URL adresų užbaigimas
+
+
+ Lankomiausioms svetainėms
+
+
+ Įjungus, „%s“ automatiškai adreso lauke užbaigs virš 450 populiarių URL adresų.
+
+
+ Svetainėms, kurias pridedate
+
+
+ Įjungus, „%s“ automatiškai užbaigs jūsų mėgiamus adresus.
+
+
+ Tvarkyti svetaines
+
+
+ Tvarkyti svetaines
+
+
+ + Pridėti savą adresą
+
+
+ Jūsų automatinio užbaigimo sąrašas:
+
+
+ Pridėti URL
+
+
+ Savo adreso pridėjimas
+
+
+ Pridėti savą adresą
+
+
+ Įtraukti saitą į automatinį užbaigimą
+
+
+ Slapukai ir svetainių duomenys
+
+
+ Duomenų siuntimas
+
+
+ Savų adresų šalinimas
+
+
+ Sužinoti daugiau
+
+
+ Pridėkite ir valdykite savo automatinio užbaigimo adresus.
+
+
+ Pridedamas adresas
+
+
+ Įdėkite arba įveskite URL adresą
+
+
+ Pavyzdys: mozilla.org
+
+
+ Pavyzdys: example.com
+
+
+ Pridėtas naujas adresas.
+
+
+ Pašalinti
+
+
+ Pašalinti
+
+
+ Patikrinkite įvestą URL adresą.
+
+ Kalba
+
+ Sistemos numatytoji
+
+ Privatumas
+ Blokuoti reklaminius stebėjimo elementus
+ Kai kurios reklamos stebi svetainių aplankymus, net jeigu ir nespustelite jų.
+ Blokuoti analizės stebėjimo elementus
+ Naudojama rinkti, analizuoti ir matuoti tokias veiklas, kaip bakstelėjimai ir slinkimas
+ Blokuoti socialinius stebėjimo elementus
+ Įterpiama svetainėse, norint stebėti jūsų apsilankymus ir rodyti tokį funkcionalumą, kaip dalinimosi mygtukai
+ Blokuoti kitus turinio stebėjimo elementus
+ Įjungus gali sutrikti kai kurių tinklalapių veikimas
+ Blokuoti slapukus
+
+
+ Ačiū, ne
+ Blokuoti tik trečiųjų šalių stebėjimo slapukus
+ Blokuoti tik trečiųjų šalių slapukus
+ Blokuoti tarp svetainių veikiančius slapukus
+ Tikrai taip
+
+
+ Atrakinti programą piršto atspaudu
+
+
+ Slaptumas
+
+ Slėpti tinklalapius pereinant tarp programų bei neleisti daryti ekrano nuotraukų.
+
+ Saugumas
+
+ Našumas
+ Blokuoti saityno šriftus
+
+ Galite nematyti kai kurių piktogramų arba paveikslų
+
+ Blokuoti „JavaScript“
+
+ Tinklalapiai bus įkeliami greičiau, tačiau gali sutrikti jų veikimas
+
+
+ Paskirti „%1$s“ numatytąja naršykle
+
+ Mozilla
+ Siųsti naudojimo duomenis
+
+
+ Sužinoti daugiau
+
+
+ „Mozilla“ siekia rinkti tik tai, ko reikia norint pagerinti „%1$s“ visiems.
+
+
+ Privatumo pranešimas
+
+
+ Apie „%1$s“
+
+
+ Įdiegtos ieškyklės
+
+
+ Pasirinkite ieškyklę
+
+
+ Atkurti numatytąsias ieškykles
+
+
+ + Pridėti kitą ieškyklę
+ Pašalinti ieškykles
+ Pašalinti
+
+
+ Pridėti kitą ieškyklę
+
+ Pasirinkite norimą ieškyklę:
+
+
+ Pridėti ieškyklę
+
+ Ieškyklės pavadinimas
+ Paieškos eilutės struktūra
+ Įrašyti
+
+
+ Pavyzdys: example.com/search/?q=%s
+
+ Pridėta nauja ieškyklė.
+
+ Įveskite ieškyklės pavadinimą
+ Jau yra įdiegta ieškyklė su tokiu pavadinimu.
+
+ Įveskite paieškos eilutę
+
+ Pasitikrinkite, ar paieškos eilutė atitinka formatą iš pavyzdžio
+
+
+ Išvalyti įvestą tekstą
+
+
+ Paslėpti
+
+
+ Valyti naršymo žurnalą
+
+
+ Atverta kortelių: %1$s
+
+
+ Saugus ryšys
+
+
+ Įkeliama
+
+
+ Svetainė įkelta
+
+
+ Daugiau parinkčių
+
+
+ Mygtukas „Daugiau parinkčių“
+
+
+ Eiti pirmyn
+
+
+ Įkelti svetainę iš naujo
+
+
+ Grįžti atgal
+
+
+ Nutraukti svetainės įkėlimą
+
+
+ Grįžti į ankstesnę programą
+
+
+ Viso užblokuotų elementų
+
+
+ Blokuokite stebėjimo elementus
+
+ Jūsų teisės
+
+ Atverti saitą kitoje programoje
+
+ Galite palikti „%1$s“, norėdami atverti šį saitą su „%2$s“.
+
+ Suraskite programą, kuri gali atverti saitą
+
+ Nė viena iš jūsų įrenginyje esančių programų negali atverti šio saito. Galite palikti „%1$s“, norėdami ieškoti tinkamos programos per „%2$s“.
+
+ Baigti privatųjį naršymą?
+
+
+ %1$s atsiųstas
+
+
+ Atverti
+
+
+
+
+
+
+
+
+
+ Nerastas serveris
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pagerinkite savo privatumą
+
+ Pereikite į aukštesnį privačiojo naršymo lygį. Blokuokite reklamas ir kitą jūsų naršymą galintį stebėti turinį, tuo pačiu sutrumpindami svetainių įkėlimo laiką.
+
+
+ Jūsų paieška, jūsų skoniui
+
+ Ieškote kažko kito? Pasirinkite kitą numatytąją ieškyklę nustatymuose.
+
+
+ Pridėkite leistukų į savo pradžios ekraną
+
+ Greitai grįžkite į savo mėgstamiausias svetaines „%1$s“ naršyklėje. Tiesiog pasirinkite „Įtraukti į pradžios ekraną“ iš „%1$s“ meniu.
+
+
+ Įpraskite prie privatumo
+
+ Paskirkite „%1$s“ savo numatytąja naršykle ir naudokitės privačiojo naršymo privalumais atverdami svetaines iš kitų programų.
+
+ Gerai, supratau!
+ Praleisti
+ Toliau
+
+
+ –
+
+
+ Pridėti
+
+
+ TAIP
+
+
+ Atsisakyti
+
+
+ NE
+
+
+ Leistukas bus atveriamas su išjungta išplėsta apsauga nuo stebėjimo
+
+
+ Privačiojo naršymo seansas
+
+
+ Pranešimai leidžia išvalyti jūsų „%1$s“ seansą bakstelėjus. Jums nereikia atverti programos ar matyti, kas atverta naršyklėje.
+
+
+ Valyti naršymo žurnalą
+
+
+ Parsisiųsti „Firefox“
+
+
+
+
+
+
+
+
+ „Mozillos“ viešąja licencija ir kitomis atviromis licencijomis.]]>
+
+
+ čia.]]>
+
+
+ licencijomis.]]>
+
+
+ „GNU General Public License v3“ licencija, ir pasiekiamus čia .]]>
+
+
+ Naudotojo vardas
+ Slaptažodis
+ Išvalyti
+
+
+
+ Saugus ryšys
+ Nesaugus ryšys
+
+ Tai liudija: %1$s
+
+
+ Svetainės saugumas
+ Toks adresas jau pridėtas
+
+
+ Rasti tinklalapyje
+
+
+ Rasti tinklalapyje
+
+
+ %1$d/%2$d
+
+ %1$d iš %2$d
+
+
+ Rasti tolesnį
+
+ Rasti ankstesnį
+
+ Paslėpti radimo funkcijas
+
+
+
+
+ Prašyti ne mob. svetainės
+
+
+ Komp. svetainė
+
+
+ Adresas nukopijuotas
+
+
+ Programuotojo priemonės
+
+
+ Atverti saitus programose
+
+
+ Kitkas
+
+
+ Svetainių leidimai
+
+
+ Automatinis grojimas
+
+
+ Leisti garsus ir vaizdo įrašus
+
+
+ Blokuoti tik garsus
+
+
+ Rekomenduojama
+
+
+ Blokuoti garsus ir vaizdo įrašus
+
+
+ Tyrimai
+
+
+ „Firefox“ retkarčiais gali įdiegti ir vykdyti tyrimus.
+
+
+ Sužinoti daugiau
+
+
+ Norint pritaikyti pakeitimus, programa bus uždaryta
+
+
+ Pašalinti
+
+
+ Aktyvūs
+
+
+ Užbaigti
+
+
+ Nuotolinis derinimas per USB arba belaidį tinklą
+
+
+ Atverti saitą naujame seanse
+
+
+ Piršto atspaudo piktograma
+
+
+ Neatpažintas piršto atspaudas. Bandykite dar kartą.
+
+
+ Pirštas sujudėjo per greitai. Bandykite dar kartą.
+
+
+ Rodyti paieškos žodžių siūlymus?
+
+
+ Kad gautų siūlymus, „%1$s“ turi persiųsti ieškyklei viską, ką rašote į adreso lauką.
+
+
+ Ne
+
+
+ Taip
+
+
+ Kai kurios ieškyklės negali rodyti paieškos žodžių siūlymų.
+
+
+ Paslėpti
+
+
+
+
+ Svetainė veikia netinkamai?\n Pabandykite išjungti apsaugą nuo stebėjimo
+
+
+ Įtraukti į pradžios ekraną]]>
+
+
+ Atverkite visus saitus „%1$s“ programoje\n Paskirkite „%1$s“ numatytąja naršykle
+
+
+ Automatiškai užbaikite lankomiausių svetainių adresus\n Spustelėkite ir palaikykite bet kurį URL adresą adreso lauke
+
+
+ Atverkite saitą naujoje kortelėje\n Spustelėkite ir palaikykite bet kurį saitą tinklalapyje
+
+
+ Išjungti patarimus pradžios ekrane
+
+
+ Atverta nauja kortelė
+
+
+ Pereiti
+
+
+ Pereiti į saitą naujoje kortelėje nedelsiant
+
+
+ Blokuoti galimai pavojingas ir apgaulingas svetaines
+
+ Blokuoti žinomas apgaulingas, kenksmingas, nepageidaujamą programinę įrangą siūlančias svetaines.
+
+
+ Tik HTTPS veiksena
+
+ Siekiant didesnio saugumo, automatiškai bandoma jungtis prie svetainių naudojant šifruotą HTTPS protokolą.
+
+
+ Išimtys
+
+ Šiose svetainėse esate išjungę turinio blokavimą.
+
+ Pašalinti
+
+ Pašalinti visas svetaines
+
+
+ Blokuoti slapukus
+
+
+ Ar norite blokuoti slapukus?
+
+
+ Kortelė užstrigo
+
+ Atsiprašome. Su šia kortele yra problemų.
+
+ Kaip privačioji naršyklė, mes nesaugome ir negalime atkurti šios kortelės.
+
+ Užverti kortelę
+
+
+
+
+
+ Pranešti apie strigtį „Mozillai“
+
+
+
+
+ Stebėjimo elementai, užblokuoti nuo %s
+
+ Turinio
+
+ Reklaminiai
+
+ Socialiniai
+
+ Analitikos
+
+ Išplėsta apsauga nuo stebėjimo
+
+ Apsaugos šioje svetainėje IŠJUNGTOS
+
+ Apsaugos šioje svetainėje ĮJUNGTOS
+
+ Ryšys saugus
+
+ Ryšys nesaugus
+
+ Blokuojami stebėjimo elementai ir scenarijai
+
+
+ Eiti atgal
+
+
+
+ Pašalinti
+
+
+ Pervadinti
+
+ Pervadinti
+
+ Leistuko pavadinimas
+
+
+ Įrašyti ir pasidalinti paveikslai <b>nebus</b> pašalinti jums išvalius „%1$s“ žurnalą
+
+
+
+ Grafinis apvalkalas
+
+ Šviesus
+
+ Tamsus
+
+ Parinkti pagal baterijos lygį
+
+ Naudoti įrenginio grafinį apvalkalą
+
+
+
+ Užverti kortelę
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-meh/strings.xml b/mobile/android/focus-android/app/src/main/res/values-meh/strings.xml
new file mode 100644
index 0000000000..827fc274cb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-meh/strings.xml
@@ -0,0 +1,1105 @@
+
+
+
+
+
+
+
+
+ Ntuvi
+
+ OK
+
+ Chuva´a
+
+
+ Nánuku a xíín chu´un iin nuu
+
+ Nánuku yɨɨ yu´u.
+
+
+ Daa nnánukunu nnaá.
+
+ Nná da a nnánukunu
+
+
+ Daa nnánukunu nnaá nuu pestaña.
+
+
+ Nánuku %1$s
+
+
+ Kua´a…
+
+
+ Iyo iin tixi nuu sitio ya´a
+
+
+ Síne jii %1$s
+
+
+ Síne jii…
+
+
+ Tee-ma nuu kajie´e
+
+
+ Tee nuu acceso ñama
+
+ Xita da acceso ñama
+
+
+ Ke´i
+ Jiee ya´a
+ Chinei
+ Daa tutu nichi no´o
+
+
+ Daa a nita noo´o nxita
+
+
+ Dee xita ya´a kuvi nuvi daa ntu iyo tiñu
+
+
+ Nakasɨ contenido
+
+ Xita ya´a saa kuvi nuvi daa sitios
+
+
+ Ya´a nsa´a %1$s
+
+
+ Kua´a jii
+
+ Xiná daa nnánukunu?
+ Kuaxin a xíín xina notificación sa naa a nnánukunu.
+
+
+ Kuaxin a xíín xina notificación sa naa a nnánukunu.
+
+ Xiná daa nnánukunu
+
+
+ Síne
+
+
+ Xiná jee síne
+
+
+ Xiná
+
+
+ Xiná daa nnánukunu
+
+
+
+ Xita jee síne
+
+
+ Xita jee síne %1$s
+
+
+
+ Nánuku ji Focus
+
+ Nánuku ji Klar
+
+ Nánuku ji Focus Beta
+
+ Nánuku ji Focus Nightly
+
+
+ %1$s taji kajinu.
+Ni´ima kua iin nuu nánuku yu´u:
+
+ Nánuku jee kaka nuu ka̱a̱ ya´a
+ Chiyu´u daa a nita no´o (a xíín kaji kuvi kuntadaja noo´o)
+ Xina saa kuvi naá cokies jii daa tu´un nnánukunu
+
+
+%1$s kuviji iin nsa´a Mozilla. Nukudani iin internet vii jee va´a.
+Ka´vi ku´eka
]]>
+
+
+ Privacidad jii seguridad
+
+
+ Kaji kuntada noo´o, cookies jii datos
+
+
+ Tee kua predeterminado
+
+
+
+
+ Jiee ya´a %1$s, chineí
+
+
+ Vitan jee vaji iin a kumi noo´o nkutada a sá´ánu
+
+
+ Tu´un Web
+
+
+ Sama ka̱a̱
+
+
+ Ntaka
+
+
+ Ka̱a̱ kua vaji, tu´un
+
+
+ Nastutu datos jii a ni´i
+
+ Nánuku
+
+
+ Ni´in tu´un nánuku
+
+ %1$s chuichi a tenu nuu ka̱a̱ nánuku
+
+
+ Kua vaji
+
+
+ Ka̱a̱ nánuku
+
+
+ Tɨɨn
+
+
+ Xina´va
+
+
+ URL Tee ntu´u
+
+
+ Daa sitios tuvika
+
+
+ Ke´í saa %s kutuvi nuu ka̱a̱ nánuku kuaiyo da 450 URLs populares.
+
+
+ Nuu daa sitios chisonu
+
+
+ Ke´i saa %s kutuvi da URLs favoritas.
+
+
+ Tetiñu daa sitios
+
+
+ Tetiñu daa sitios
+
+
+ +Tee a sá´á vii URL
+
+
+ Lista noo´o autocompletado:
+
+
+ Tee URL
+
+
+ Tee URL vii
+
+
+ Tee a sá´á vii URL
+
+
+ Tee enlace saa kuvi tee ntu´u
+
+
+ Cookies jii datos nuu ya´a
+
+
+ Kaji datos
+
+
+ Náxitá URLs a sá´ávii
+
+
+ Kutu´va-ka
+
+
+ Tee jee tetiñu de kuvi tee ntu´u URL sá´á vii.
+
+
+ URL a tee
+
+
+ Tee a xíín chu´un URL
+
+
+ Kua: mozilla.org
+
+
+ Kua: nakuvi.com
+
+
+ A jíía vii URL ntee.
+
+
+ Xita
+
+
+ Xita
+
+
+ Natee tuku URL nteenu.
+
+ Tu´un
+
+ Kua vaji ka̱a̱
+
+ Yɨ´ɨ yu´u
+ Chiyu´u daa tutu xiko nantiñu
+ Daa tutu xiko, sunika ntu sinenuma, nitada noo´o daa nnánukunu nuu web
+ Chiyu´u daa ka̱a̱ nita noo´o
+ Ni´idama jee xitutuda, jee chunku´va a sá´á jii a ke´i
+ Chiyu´u daa ka̱a̱ sociales
+ Yɨ´ɨ nuu daa web saa kuvi kuntada noo´o jee kutuvi daa botones kuvi kua´a
+ Chiyu´u daa ka̱a̱ nita daa inka contenido
+ Dee tee ya´a kuvi sá´á daa web nsatiñu vii
+ Kasɨ cookies
+
+
+ Ntuvi, kuta´vini
+ Nakasɨ 3 cookies daa inka kuvi kuntada noo´o
+ Nakasɨ ntu xini cookies daa inka
+
+ Kasɨ cookies jíí da sitios
+ Kuvi
+
+
+ Ni´in ka̱a̱ na´a tɨɨ saa kuvi síne ka̱a̱ ya´a
+
+
+ Síne jíí na\'a tiin noo\'o de nchisonu accesos directos a xíín sa íyo nune iin sitio jíí %s.
+
+
+ Ntu tuvi
+
+ Chiyu´u daa tutu web saa sama ka̱a̱ jee chiyu´u ka̱a̱ natava.
+
+ Kumiji noo´o
+
+ Naja satiñu
+ Chiyu´u tu´un web
+
+ Kuvi tuvi íconos a xíín nkutuvi tutu natava
+
+ Kasɨ JavaScript
+
+ Daa página kuvi kivu ñama, jee kuvi nsatiñu vii
+
+
+ Sá´á %1$s navegador xinañu´u ya´a
+
+ Mozilla
+ Chu´uichí datos a ni´í
+
+
+ Ka´vi kue´eka
+
+
+ Mozilla nake´e sani daa a íyo tiñu jee jia´a va´a %1$s nuu kuaiyo daa yo.
+
+
+ Tutu xítu
+
+
+ Tutu nákani saa kuantiñunu ya´a
+
+
+ Bibliotecas a ni´idani
+
+
+ %s | Bibliotecas OSS
+
+
+ Jiee ya´a %1$s
+
+
+ Ka̱a̱ nánuku yɨ´ɨ
+
+
+ Kaji inka ka̱a̱ nánuku
+
+
+ Na tee tuku ka̱a̱ nánuku kua vaji
+
+
+ + Tee inka ka̱a̱ nánuku
+ Xita ka̱a̱ nánuku
+ Xita
+
+
+ Tee inka ka̱a̱ nánuku
+
+ Kaji ka̱a̱ nánuku kuvi ni´inu:
+
+
+ Chisó ka̱a̱ nánuku
+
+ Sivɨ ka̱a̱ nánuku
+ Nuku tu´un saa ni´ima
+ Chuva´a
+
+
+ Kua: example.com/search/?q=%s
+
+ A jíía ka̱a̱ nánuku ntee.
+
+ Tee sivɨ ka̱a̱ nánuku
+ Iin ka̱a̱ nánuku a ni´i sivɨ ya´a.
+
+ Tee a nánukunu
+
+ Kune´ya dee tu´un nánukunu íyo kua vají nuu ya´a
+
+
+ Xinó tu´u nchu´u
+
+
+ Chiyu´u
+
+
+ Xinó daa nnánuku noo´o
+
+
+ Pestañas nune: %1$s
+
+
+ Conexión vatu
+
+
+ Kunetu
+
+
+ Nuu web kuvi tuvi
+
+
+ Kue´e-ka nuu kají
+
+
+ Botón nuu kaji
+
+
+ Kaka ichí nuu
+
+
+ Na chisó tuku web
+
+
+ Niko yata
+
+
+ Kunetu nee kutuvi web ya´a
+
+
+ Niko nuu ka̱a̱ a nee yata
+
+
+ Tuni daa nita noo´o nxita
+
+
+ Chiyu´u daa web nita noo´o
+
+ Tutu nichí noo´o
+
+ Síne link nuu inka ka̱a̱
+
+ Kuvi kenenu nuu %1$s jee síne link ya´a nuu %2$s.
+
+ Nánuku iin ka̱a̱ nuu kuvi síne link ya´a
+
+ Ni´i daa ka̱a̱ nuu ka̱a̱ noo´o kuvi síne link ya´a. Kuvi kenenu nuu %1$s saa kuvi nánukunu iin ka̱a̱ kuvi koo inu jii %2$s.
+
+ Kene nuu nánuku daa yɨ´ɨ yu´u?
+
+
+ %1$s nnɨ´ɨ
+
+
+ Síne
+
+
+
+
+
+
+
+
+
+
+ Tee nuu acceso ñama!
+
+ Ntu nani´í ka̱a̱ servidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nakasɨ
+
+
+
+ Kusɨɨ inidáni nkantanu nu %1$s
+
+
+ Ñama. Iyo yu´u. Ntu iyo tixi.
+
+
+ Kajie´e
+
+
+
+ %1$s ntu kuviji kua da inka ka̱a̱ nánuku
+
+
+ Xinadani a nnánukunu ntaka ichi nakasɨnɨ ka̱a̱
+
+
+
+ Tee %1$s kua predeterminado saa kuvi chuva´a da datos noo´o jii ntaka enlace sinenu.
+
+
+ Sá´á ka̱a̱ ya´a iin a ni´inu ntaka íchi
+
+
+ Nava
+
+
+ Sika privacidad noo´o
+
+ Ni´i nánuku yu´u inka nivel. Xita daa tutu kuvi xiko a xíín daa nita noo´o, saa´a ntu jika ñama ka̱a̱ nánuku.
+
+
+ Nánuku, kua kachi noo´o
+
+ Nánukunu inka tu´un? Kaji inka ka̱a̱ nánuku nuu daa ke´í.
+
+
+ Tee a kivu ñama nuu kajie´e
+
+ Niko ñama nuu daa sitios ta´a ini noo´o %1$s. Kaji \"tee nuu kajie´e\" nu %1$s.
+
+
+ Kino jii privacidad
+
+ Tee %1$s kua ka̱a̱ xinañu´u jee kune´ya a taji saa nánuku yu´unu saa sinenu nuu daa inka ka̱a̱.
+
+ OK, vatu!
+ Nava
+ Inka
+
+
+ -
+
+
+ Tee
+
+ KUVI
+
+
+ Nkuvi
+
+ NTUVI
+
+
+ Ya´a kune jíí iin a kumi nkutadaja a sá´ánu
+
+
+ Nánuku yu´u
+
+
+ Daa notificación kuvi xita daa sesión nuu %1$s nejika kuaxinu. Ntu nejika sinenu ka̱a̱ saa kune´yanu a nune nu ka̱a̱ nánuku.
+
+
+ Náxitá sɨ´ɨ nnánuku
+
+
+ Xinuun Firefox
+
+
+
+
+
+
+
+
+ tutu xitu Mozilla jíí da inka tutu código nune.]]>
+
+
+ ya\'a.]]>
+
+
+ licencias nune jee kuvi sama código.]]>
+
+
+ tutu xitu ntaka GNU v3 jee kuvi nani´inuma ya´a .]]>
+
+
+ Sivɨ
+ Contraseña
+ Xinoo
+
+
+
+ Conexión vatu
+ Ntu va´a conexion
+
+ Nne´ya: %1$s
+
+
+ Seguridad nuu ya´a
+ URL ya´a a íyo
+
+
+ Nánuku nuu página
+
+
+ Nánuku nuu página
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Nánuku inka tu´un
+
+ Nuku tu´un ichi yata
+
+ Nakasɨ nnánukunu
+
+
+
+
+ Síne ñama ya´a
+
+
+ Sitio de escritorio
+
+
+ URL ntɨ
+
+
+ Ka̱a̱ ñɨvɨ ke´i ya´a
+
+
+ Síne enlaces jíí aplicaciones
+
+
+ A biji
+
+
+ Permisos del sitio
+
+
+ Reducción de banner de cookies
+
+
+ Tɨɨn
+
+
+ Xina´va
+
+
+ Reducción de banner de cookies
+
+
+ Kune´ya mati´i banners de kuvi nkua´anu jia´a da cookies.
+
+ -->
+ Reducción de banner de cookies
+
+
+ ACTIVADO nu sitio ya´a
+
+
+ Sitio ntu satiñu vatu ntañu´u
+
+
+ DESACTIVADO nu sitio ya´a
+
+
+ Reducción de banner de cookies
+
+
+ DESACTIVADO nu sitio ya´a
+
+
+ ACTIVADO nu sitio ya´a
+
+
+ ¿A kuvi sa´a reducción banner da cookies nu %1$s?
+
+
+ ¿A kuvi sa´a reducción banner da cookies nu %1$s?
+
+
+ %1$s Naa daa cookies sitio ya\'a jee nakajie\'e tuku tutu. Dee naa kuaiyo daa cookies kuvi nakasɨ sesión noo\'o a xíín naa daa ka̱a̱ nuu ke\'en.
+
+
+ %1$s kuvi sá´á njia´a da solicitudes jikan cookies.
+
+
+ Ntañu\'un, nuu ya\'a ntu satiñu va\'a jíí reducción banner jíí cookies. ¿A kuvinu kakanu nuu daa nuu\'u kune\'yadani nuu web ya\'a saa kuvi satiñu va\'a íchi nuu?
+
+
+ Nkuvi
+
+
+ Chinei nuu´u
+
+
+ Tutu a chineida noo´o nchu´un ichi
+
+
+ Tutu a chineida noo´o nchu´un ichi
+
+
+
+ %1$s Nkua\'anu jia\'a daa solicitudes cookies saa naa daa banners cokies.\n
+\n Ke\'i daa preferencias banners cookies nu %2$s.
+
+ Da nu ke´i
+
+
+ Reproducción automática
+
+
+ Sa kuvi:
+
+
+ 1. Kua´an nuu ke´i Android
+
+
+ Permisos]]>
+
+
+ Kua´an nuu da ke´i
+
+
+ %1$s a stuu]]>
+
+
+ Ka̱a̱ natava
+
+
+ Micrófono
+
+
+ Nuu iñɨnu
+
+
+ Notificación
+
+
+ Contenido controlado por DRM
+
+
+ Kakan permiso
+
+
+ Nasɨ
+
+
+ Kuvi
+
+
+ Nasɨ jiee Android
+
+
+ Kuvi koo nasun ji tutu kana
+
+
+ Kasɨ ntuxini nasun
+
+
+ Recomendado
+
+
+ Kasɨ nasun ji tutu kana
+
+
+ Estudios
+
+
+ Firefox kuvi chu´un je sá´á estudios ntaka ichi.
+
+
+ Kuni kue´eka
+
+
+ Aplicación nejika nakasɨ sa kuvi tuvi a jíía
+
+
+ Xita
+
+
+ Activo
+
+
+ Completados
+
+
+ Depuración remota jii USB/Wi-Fi
+
+
+ Sine tuku
+
+
+ Kachi de kuvi jii huella digital
+
+
+ Kuvi niínu huella digital sa kuvi kaji´enu sesión nu ka̱a̱
+
+
+ Síne enlace nuu a jíía sesión
+
+
+ Tutu huella digital
+
+
+ Ntu nnakuni huella digital. Tee ichika.
+
+
+ Xinti nkana ñama. Tee ichika.
+
+
+ ¿Xituvi da sugerencias a nnánuku?
+
+
+ Saa kuvi ni´inu tu´un chineí nánuku, %1$s nejika chu´un ichinu daa a teenu nu chu´un direcciones jii ka̱a̱ nánuku.
+
+
+ Ntuvi
+
+
+ Kuvi
+
+
+ Daa ka̱a̱ nánuku ntu nkuvi xituvi daa tu\'un chineí nánukunu.
+
+
+ Xita
+
+
+
+
+ ¿Ntu satiñu vii sitio ya´a?\n Xita a kumi nkuntada-ja noo´o
+
+
+ Tee nuu kajie´e]]>
+
+
+ Sine daa enlace nuu %1$s\n Tee %1$s kua ka̱a̱ xinañu´u
+
+
+ Tee saa kuvi naku´un ini kaa daa URLs\n Kuaxin iin URL nuu chu´un daa direcciones
+
+
+ Sine iin enlace nuu pestaña jíía\n Kuaxin iin enlace nuu página
+
+
+ Xita tu´un chineí nánukunu nuu kajie´e
+
+
+ A jíía pestaña nsine
+
+
+ Sama
+
+
+ Kivunɨ nu pantalla ka´nu
+
+
+ Sama kuvi chunta´a pestaña jíía ñama
+
+
+ Tee ntuvi daa sitios tixin jiin sitios kantu´un
+
+ Tee ntuvi daa sitios kuvi sa´a xeen, jee daa sitios kumi ka̱a̱ ntu íyo vatu.
+
+
+ Ntu xini HTTPS
+
+
+ Nakivɨ tuku nuu daa sitio jee ni\'i protocolo encriptación HTTPS saa koo kue\'eka a kumiji noo\'o saa jikanu nuu ka̱a̱ ya\'a.
+
+
+ Exceptions
+
+ Nxitanu nkutuvi tu´un nuu daa sitios ya´a.
+
+ Xita
+
+ Xita kuaiyo daa sitios
+
+
+ Kasɨ cookies
+
+
+ ¿A kuvinu kasɨ da cookies?
+
+
+ Ntivu pestaña
+
+ Nei koo ini-nu. Iyo iin tixi jiin pestaña ya´a.
+
+ Ka̱a̱ ya´a. ntu chuva´a jee nkuvi sine pestaña ni´inu ichi yata.
+
+ Nakasɨ pestaña
+
+
+
+
+
+ Chu´un ichi saa tivu ya´a nuu Mozilla
+
+
+
+
+ Rastreadores nnakasɨ ne %s
+
+ Contenido
+
+ Tutu xiko
+
+ Social
+
+ Analítica
+
+ Vitan jee vaji iin a kumi noo´o nkutada a sá´ánu
+
+ Da a kumi noo´o ntu iyo activo nu sito ya´a
+
+ Da a kumi noo´o iyo activo nu sito ya´a
+
+ Conexión va´a
+
+ Conexión ntu va´a
+
+
+ Rastreadores y scripts a nejika nakasɨ
+
+
+ Xniko
+
+
+
+ Xita
+
+ Natee
+
+ Natee
+
+
+ Sivɨ acceso ñama
+
+
+ Da tutu nátava a nchuva´a je nsajia<b>nná</b> de xinanu da tu´un nnánukunu %1$s
+
+
+
+ Tema
+
+ Niji
+
+ Teku tun
+
+
+ Tee sa ntaxin bateria
+
+ Ni´i tema ka̱a̱
+
+
+ Ntu satiñu vatu jii HTTPS
+
+
+ Kuni kue\'eka jiee
+ Sama configuración nuu ajustes > Privacidad & Seguridad > Seguridad.]]>
+
+
+ Conexión ntu va´a
+
+
+
+ Dee a kivɨ-nu servidor ya´a íchi yata sa̱naan kuvi iin error nkene ntañu\'u, kuvi nakivɨ-nu íchika dee nníní.
+ ]]>
+
+
+ Sana íyo ñivɨ kuvi kantu\'un noo\'o nuu sitio ya\'a de jia\'nu kuvi koo tixi.
+
+ %1$s no confía en %2$s porque el emisor del certificado es desconocido, el certificado está autofirmado o el servidor no envía los certificados intermedios correctos.
+ ]]>
+
+
+
+ Nakasɨ pestaña
+
+
+
+ ¡Vatu! Nsá´ádani a página web ya´a nkune´ya a sá´ánu. Kuaxin nu escudo saa kuvi kuninu da a nakasɨdani.
+
+
+ Nakasɨ elemento emergente
+
+
+
+ ¡Kumidani noo´o!
+
+ Configuración predeterminada jia\'a iin a kumi. Ntu viji kuvi kei\'o saa kuvi koo kua kuvinu ni\'inuma
+
+ Xita
+
+
+ Kuaxin ya´a sa kuvi xina kuaiyo — a nánukunu, cookies, je da inka — kajie´e nu iin pestaña jíía.
+
+
+
+
+ Nakasɨ
+
+
+ Widget a nánuku
+
+
+ ¡Nná da a nnánukunu! 🎉
+
+
+ Kajie´e sesión yi yu\'u jee nakasɨdani rastreadores jíí inka amenazas ntaka jikanu nuu ka̱a̱ ya\'a.
+
+
+ Xinodani jíí navegación yu\'u, je kajie\'e ñama inka íchi jíí widget %1$s nuu pantalla xinañu\'u.
+
+
+ Chisó ya´a nuu kajie´e xinañu´un
+
+
+ Widget nte nu pantalla xinañu´u
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-mix/strings.xml b/mobile/android/focus-android/app/src/main/res/values-mix/strings.xml
new file mode 100644
index 0000000000..03d0e45ea3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-mix/strings.xml
@@ -0,0 +1,1072 @@
+
+
+
+
+
+
+
+
+ Kuntyatu
+
+ Va\'a
+
+ Tyika vaà
+
+
+ Ndukú a tyaa ña kunu kuncheu
+
+ Ndukù Sèè. Kunchee. Sto\'o. Ntakua.
+
+
+ Sto\'i ña ntsinu.
+
+ Stoo ña ntsinu
+
+
+ Stoi ña ntsinu ña ntsika nu xikua.
+
+
+ Ndukù %1$s
+
+
+ Stucha…
+
+
+ Ka tu\'un ña kue va\'a yee sitio\'yo
+
+
+ Kuna tsi %1$s
+
+
+ Kuna tsi…
+
+
+ Chika\'a nu pantalla xina
+
+
+ Tyika xina
+
+ Stoó ña xina
+
+
+ Nu samu
+ Tsaa ña
+ Chinchee
+ Yee yu\'u
+
+
+ Ntasi nu kuu snuu kue ña\'a
+
+
+ Ndavi tatu kue ku nduva sitio yo
+
+
+ Kasi kontenido
+
+ Ndavi takua ku nduvaa sitio yoo
+
+
+ Nikuvai tsi %1$s
+
+
+ Kua’a nu\'u
+
+ Stòo ña ndúkuku sata
+
+ Stòo ña ntukuku sata
+
+
+ Kuna
+
+
+ Stoo cha Kuna
+
+
+ Stoo
+
+
+ Stòo ña ndukuku sata
+
+
+
+ Stoo ra kuna
+
+
+ Stoo tsi kuna %1$s
+
+
+
+ Nduku nu Focus
+
+
+ Nduku nu Klar
+
+ Nduku tsi Focus Beta
+
+ Nduku tsi Focus Nightly
+
+
+ %1$s te da el control.
+Usalo como navegador privado:
+
+ Buscar y navegar directamente en la aplicación
+ Bloquear rastreadores (o actualizar los ajustes para permitir rastreadores)
+ Borrar para eliminar cookies e historial de búsqueda y navegación
+
+
+%1$s es producido por Mozilla. Nuestra misión es promover una Internet saludable y abierta.
+Conocer más
]]>
+
+
+ Ña i\'í tsi Ña kunka vai
+
+
+ Lista ña rastreo, cookies tsi tutu
+
+
+ Chikai tana xina, cha ntacha\'a mií ña
+
+
+
+
+ Tu\'un tsa %1$s, ña chinhe\'e yo
+
+
+ Protección de rastreo aumentada
+
+
+ Tutu web
+
+
+ Sama kue aplicacion
+
+
+ Ntií
+
+
+ Navegador xina, tu un
+
+
+ Satutu kue tutu cha nchi vai
+
+ Nduku
+
+
+ A Kuncheu nixi ndukuku
+
+ %1$s ku chachuin nchi ña ku nchau nu barra de direcciones tsi ña ku ndukuku
+
+
+ Tsa sa\'an yee
+
+
+ Ña nduku
+
+
+ Kuna
+
+
+ Kasi
+
+
+ Chaa URL
+
+
+ Sitio kua ncheu
+
+
+ Habilitar para que %s autocomplete más de 450 URLs populares en la barra de direcciones.
+
+
+ Sitio chikau
+
+
+ Habilitar para que %s autocomplete tus URLs favoritas.
+
+
+ Kunche sitio
+
+
+ Kunche sitio
+
+
+ + Chaa URL
+
+
+ Tu lista de autocompletado:
+
+
+ Tyika URL
+
+
+ Chaa URL
+
+
+ Chaa URL
+
+
+ Chika enlace takua na tsinu mitu’ìn
+
+
+ Cookies tsi tutu sitio
+
+
+ Katsi tutù
+
+
+ Stòo URLs nchau
+
+
+ Skua\'a kuakaa
+
+
+ Chaa a sama URLs tana kuni meu.
+
+
+ Chaa URL
+
+
+ Chistiin a chaa URL
+
+
+ Tana: mozilla.org
+
+
+ Kua: sivi.com
+
+
+ Ntutsaa URL nchau.
+
+
+ Xitaá
+
+
+ Xitaá
+
+
+ Chaa va\'a URL.
+
+ Tu\'un
+
+ Nkoi ta xi’na
+
+ Sèè
+ Kasi ña ndukuyo
+ Yee tu\'un nduku ña tsinu, vasu ma katavu ña
+ Kasi ña ndukuyo
+ Kuachu\'un ña tava satutuku tsi chikuau ña sau tana katavu tsi samu
+ Kasi ña ndukuyo
+ Ntachiiaña nu ke sitiotava ntanii ña ntsinu cha snai boton tana kua\'á
+ Kasi ña ndukuyo inka tsio
+ Tatu na Kuachu\'un ña ku kueva\'a nuna pàguina
+ Kasi cookies
+
+
+ Ti tsavu
+ Kasi cookies ña inka nivi
+ Kasi cookies ña inkana
+
+ Bloquear cookies de sitios cruzados
+ Va´a
+
+
+ Kuchun huella digital tava kunu aplicación
+
+
+ Desbloqueá usando la huella digital si agregaste accesos directos o cuando un sitio web ya está abierto en %s.
+
+
+ Sèè
+
+ Chika sèè pagina ta ku samu aplicaciòn cha casi ña ndatava.
+
+ Vaá
+
+ Nixi sachu\'in
+ Makuau kuni siin ña\'a
+
+ Kuu kuncheu ika ña\'a icono a tutu ndatavana ña tsa koo
+
+ Kasi JavaScript
+
+ Ku yàchika kitsa kue página, ti ku ma nuna va’i
+
+
+ Saa %1$s navegador xinaa
+
+ Mozilla
+ Chachu\'un tu\'un nchachu\'un
+
+
+ Skua\'a kuakaa
+
+
+ Mozilla ndaki\'an ña vaa tava na koo vaa %1$s ntio.
+
+
+ Nixi inka vaa tutu\'ku
+
+
+ Información de licencia
+
+
+ Bibliotecas que usamos
+
+
+ %s | Bibliotecas OSS
+
+
+ Tsaa ña\'a%1$s
+
+
+ Ndu tsaa ña nda nduku
+
+
+ Katsi buscador
+
+
+ Ndu tsaa ña nda ntuku
+
+
+ + Chika´a inka motor nduku
+ Sto\'o ña ntuku
+ Xitaá
+
+
+ Tyika inka buscador
+
+ Katsi buscador va´á:
+
+
+ Chikaa tsi ña ndukuku
+
+ SIvi ña nduku
+ Nduku tu\'un kunu kuachu\'un
+ Chika vaà
+
+
+ Sa\'an chau\'ña: example.com/search/?q=%s
+
+ Ntu tsa\'a tsi ña ndukuku.
+
+ Chaa sivi ña kunu nduku
+ Tsa inka\'a iin ña nduku tsi sivi yoo.
+
+ Chaa tu\'un kunu ndukuku
+
+ Ckunche\'e a vaà nchau ña ku ndukuku
+
+
+ Stoo nu nikivu
+
+
+ Nkucha\'a su\'u
+
+
+ Stòo ña ntukuku sata
+
+
+ Xikua nuna: %1$s
+
+
+ Kuu kivu
+
+
+ Saa ki tsai
+
+
+ Kunchatu saa ku ki\'tsai
+
+
+ Kunchee nu ndasamu kue ñaa
+
+
+ Botón kuá’á
+
+
+ Nduku chinuu
+
+
+ Ntu tsaa pagina web
+
+
+ Nduku sata
+
+
+ Ma ntu tsaa pagina web
+
+
+ Nchiko sata
+
+
+ Kavi nisa rastreador ndasia
+
+
+ Kasi ña ndukuyo
+
+ Yee yu\'u
+
+ Kuna nu ndukuku inka tsio
+
+ Kuu siau %1$s cha kuu kunu nu ndukuku yoo%2$s.
+
+ Nduku inka nu kuu kuncheu ña kunu
+
+ Ni iin nu sachuun kúú kunaku tsi enlace yó’o. Kuu ketu %1$s tatu kunu ndukukuv%2$s inka nu kuu sachuun.
+
+ A keu nu nduku seu ñaa?
+
+
+ %1$s ntsinu
+
+
+ Kuna
+
+
+
+
+
+
+
+
+
+
+ Tyika xina
+
+ Kòo servidor ni ntanèe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Kasi
+
+
+
+ A nikitsau nu %1$s
+
+
+ Rápido. Privado. Sin distracciones.
+
+
+ Ki tsaà
+
+
+
+ %1$s kue inui tsi inka la nduku
+
+
+ Borramos tu historial cuando cerrás la aplicación para mayor privacidad.
+
+
+
+ Poné a %1$s como predeterminado para proteger tus datos con cada enlace que abrás.
+
+
+ Tyika Tana ña nduku xina
+
+
+ Kanta
+
+
+ Saa kanu ña sèè
+
+ Nduku sèè ña kunu. Kasi tu\'un va\'a tsi siin ñaa va\'a sa\'a kue pagina.
+
+
+ Nduku ña kunu, nixi kunu
+
+ ¿Nudukuku si\'in ña\'a? katsi inka tsiña ndukuku inka nu sama ntii ñaa yo.
+
+
+ Chikaa nixi kitsau nu pantalla xinaa
+
+ Nchiko nu inka pagina %1$s katavi. Nu xikua %1$s \" Chikaa nu pantalla xinaa \".
+
+
+ Ka\'un ña see
+
+ Chikaa %1$s nu ndukuku xina cha kunchee nixi nduku seu ta tsunaku pagina web inka tsio.
+
+ Va\'a, tsa ntakii ña!
+ Kanta
+ Nuu
+
+
+ -
+
+
+ Chikaa
+
+
+ Va´a
+
+
+ Kunchatu
+
+
+ Va´á
+
+
+ El acceso directo se abrirá con la protección de rastreo aumentada desactivada
+
+
+ Nikitsa seu
+
+
+ Tu\'un kuu stoi nu nikitsau %1$s ta katavu. ke tsiniñu\'u kunakuña ni kuncheu ta sachuùn navegador.
+
+
+ Stòo ña ntukuku sata
+
+
+ Snuu Firefox
+
+
+
+
+
+
+
+
+ Licencia Pública de Mozilla y otras licencias de código abierto.]]>
+
+
+ aquí.]]>
+
+
+ licencias gratuitas y de código abierto.]]>
+
+
+ GPLv3 (GNU General Public License v3), y se encuentra disponible aquí .]]>
+
+
+ Sivìi
+ Tu\'un seè
+ Stòo
+
+
+
+ Mani\'ku Chitai\'ña
+ Ma kuu Chitai\'ña
+
+ Verificado por: %1$s
+
+
+ Vaa sitio yoo
+ Tsa inka URL
+
+
+ Ndaduku yo\'o
+
+
+ Nduku nu página
+
+
+ %1$d/%2$d
+
+ %1$d a %2$d
+
+
+ Nduku tu\'un nu\'u
+
+ Nduku tu\'un sata
+
+ Kasi nu ndukuku
+
+
+
+
+ Kaka sitio nu escritorio
+
+
+ Xina
+
+
+ Ndatava URL
+
+
+ Nu ndasamu
+
+
+ Kuna tsi aplicación
+
+
+ Nchichi
+
+
+ Kuna sitio
+
+
+ Reducción de anuncios de cookies
+
+
+ Kuna
+
+
+ Kasi
+
+
+ Reducción de anuncios de cookies
+
+
+ Vea menos anuncios rechazando automáticamente las solicitudes de cookies cuando sea posible.
+
+ -->
+ Reducción de anuncios de cookies
+
+
+ ACTIVADA para este sitio
+
+
+ El sitio actualmente no es compatible
+
+
+ DESACTIVADA para este sitio
+
+
+ Reducción de anuncios de cookies
+
+
+ DESACTIVADA para este sitio
+
+
+ ACTIVADA para este sitio
+
+
+ ¿Habilitar reducción de mensajes de cookies para %1$s?
+
+
+ ¿Habilitar reducción de mensajes de cookies para %1$s?
+
+
+ %1$s borrará las cookies de este sitio y actualizará la página. Borrar todas las cookies puede cerrar la sesión o vaciar los carritos de compras.
+
+
+ %1$s puede intentar rechazar automáticamente las solicitudes de cookies.
+
+
+ Este sitio actualmente no soporta la reducción de mensajes de cookies. ¿Querés pedirle a nuestro equipo que revise este sitio web y agregue soporte en el futuro?
+
+
+ Kunchatu
+
+
+ Katu un
+
+
+ Pedido para soporte del sitio enviado.
+
+
+ Pedido para soporte del sitio enviado.
+
+
+
+ %1$s trata de rechazar los pedidos de cookies para descartar pancartas de cookies molestos.\n\nAdministrar preferencias de mensajes de cookies en %2$s.
+
+ Ndasama
+
+
+ Ndatu´un
+
+
+ Kuna tsi:
+
+
+ 1. Kuna nu ndasama Android
+
+
+ Sama]]>
+
+
+ Kua´an nu ku ndasamu
+
+
+ %1$s]]>
+
+
+ Cámara
+
+
+ Micrófono
+
+
+ Ubicación
+
+
+ Notificación
+
+
+ Contenido controlado por DRM
+
+
+ Kaka permiso
+
+
+ Ndakasi
+
+
+ Va’a
+
+
+ Android ntsasia
+
+
+ Kuna ndusu tsi ña ndatavana
+
+
+ Kasi ndusu
+
+
+ Recomendado
+
+
+ Kasi ndusu tsi ña ndatavana
+
+
+ Skua
+
+
+ Firefox ku tyikai la skua ya kunu.
+
+
+ Skua´a kuakaa
+
+
+ La aplicación se cerrará para aplicar los cambios
+
+
+ Xitaá
+
+
+ Va´a
+
+
+ Va´a
+
+
+ Nta sa vaà tsi USBWi-Fi
+
+
+ Kuna
+
+
+ Confirmar usando tu huella digital
+
+
+ Kuvi niínu huella digital sa kuvi kaji´enu sesión nu ka̱a̱
+
+
+ Kuna nu xikua tsàa
+
+
+ Huellas digitales
+
+
+ Kue ndakuinia huella digital. Kitsa tuku.
+
+
+ Vichi nchua ni kanta ndau. Kitsa tuku.
+
+
+ A Kuncheu nixi ndukuku
+
+
+ Para obtener sugerencias de búsqueda, %1$s necesita enviar lo que ingreses en la barra de direcciones al motor de búsqueda.
+
+
+ No
+
+
+ Va\'a
+
+
+ Yee kue motor de búsqueda kue ku snai ña ku kuncheu.
+
+
+ Chika seé
+
+
+
+
+ ¿Ntu satiñu vii sitio ya´a?\n Xita protección de rastreo
+
+
+ Tyika xina]]>
+
+
+ Kuna iin enlace tsi %1$s\n Chikay %1$s tana navegador xina
+
+
+ Ntasa URLs ña sitio ña kuaka chichu\'un\n Katavi URL niku nu barra ña direcciones
+
+
+ Kuna enlace nu xikua tsaá\n Katavi nchi enlace niku nu página
+
+
+ Xita tu´un xina ya kitsau
+
+
+ Ndutsa xikua nuna
+
+
+ Sama
+
+
+ Sama pantalla kanu
+
+
+ Sama enlazar nu xikua tsaa vityi
+
+
+ Kasi sitio vaa tsi ña stavi
+
+ Kasi sitio vaa tsi ña stavi, sitio nu inka kue aplicaciones tsi software vaa.
+
+ HTTPS
+
+
+ Kunu kuntyeu sitio tatyu un protocolo HTTPS cifrado.
+
+
+ Excepciones
+
+ Xitaá
+
+ Atoo ntii pagina web
+
+
+ Kasi cookies
+
+
+ ¿A kunu kasú cookies?
+
+
+ Vaa Xikua
+
+ Kue ku nuna xikua yoo
+
+ Ña Ntukú sèè, kue nchikavai ña ntsinu tsi xikua ntsinu sata.
+
+ Kasi xikua
+
+
+
+
+
+ Tachu\'un tu\'un ña vai nu Mozilla
+
+
+
+
+ Ntsasia reatreador nu%s
+
+ Contenido
+
+ Publicidad
+
+ Social
+
+ Analítica
+
+
+ Chikavaa ña sau
+
+ Las protecciones están desactivadas para este sitio
+
+ Las protecciones están activadas para este sitio
+
+ Conexión va´a
+
+ Conexión va´a
+
+ Rastreadores y scripts a bloquear
+
+
+ Satà
+
+
+
+ Xitaá
+
+
+ Tya sivu
+
+ Tya sivu
+
+ Sivi siki
+
+
+ Tutu ndatavu ña inka vaa compartidas <b>ma</b> sntoi ta ku stuu ña ntsinu nu %1$s
+
+
+
+ Tema
+
+ Yaa
+
+ Ntyai
+
+
+ Ntyai bateria
+
+ Tatyu´un ña inka nu kaa ndusu ku
+
+
+ Sitio yo kue ku kuni HTTPS
+
+
+ Conexión ntu va´a
+
+
+
+ Kasi xikua
+
+
+ Kasi popup
+
+
+
+ ¡A yee vaa!
+
+
+ Tyika seé
+
+
+
+
+ Kasi
+
+
+ Dnuku Widget
+
+
+ ¡Koo ña ntsinu ! 🎉
+
+
+ Kitsa tsi sesión ku ntyeé seé tya ku kasiko ku rastreador tsi ntyi ka ña\'a.
+
+
+ Te dejaremos con tu navegación privada, pero podés conseguir un inicio más rápido la próxima vez con el widget de %1$s en la pantalla de inicio.
+
+
+ Chika\'a nu pantalla xina
+
+
+ Tyika Widget xina
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-mr/strings.xml b/mobile/android/focus-android/app/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000000..801d8b028b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-mr/strings.xml
@@ -0,0 +1,680 @@
+
+
+
+
+
+
+
+
+ रद्द करा
+
+ ठीक आहे
+
+ साठवा
+
+
+ शोधा किंवा पत्ता द्या
+
+ स्वयंचलित खाजगी ब्राउझिंग.\nब्राउझ करा. मिटवा. पुन: करा.
+
+
+ आपला ब्राउझिंग इतिहास मिटवला आहे.
+ ब्राउझिंग इतिहास नष्ट
+
+
+ टॅबचा ब्राउझिंग इतिहास मिटविला गेला आहे.
+
+
+ %1$s करिता शोधा
+
+
+ शेअर करा…
+
+
+ साइट समस्येचा अहवाल द्या
+
+
+ %1$s मध्ये उघडा
+
+
+ मध्ये उघडा…
+
+
+ मुख्य पटलावर जोडा
+
+
+ शॉर्टकट मध्ये जोडा
+
+ शॉर्टकट मधून हटवा
+
+ सेटिंग
+ याबद्दल
+ मदत
+ आपले अधिकार
+
+
+ मागोवा यंत्रणा अडवली
+
+
+ हे बंद केल्याने कदाचित काही साईट समस्या सुटू शकतील
+
+
+ मजकूर अवरोधन
+
+ साईट ठीक करण्यासाठी बंद करा
+
+
+ %1$s द्वारे समर्थित
+
+
+ याद्वारे शेअर करा
+
+
+ ब्राउझिंग इतिहास खोडा
+
+
+ उघडा
+
+
+ खोडा व उघडा
+
+
+ खोडा
+
+
+ ब्राउझिंग इतिहास खोडा
+
+
+
+ मिटवा आणि उघडा
+
+
+ मिटवा आणि %1$s उघडा
+
+
+
+ %1$s आपल्याला नियंत्रण देतो.
+खाजगी ब्राउझर म्हणून वापरा:
+
+अँप मध्येच शोध व ब्राउझ करा
+मागोवा यंत्रणा अडवा(किंवा सेटिंग बदलून मागोवा यंत्रणेला परवानगी द्या)
+कुकीज, सर्च व ब्राऊझिंग इतिहास नष्ट करण्यासाठी खोडून टाका
+
+
+%1$s Mozilla ने उत्पादित केला आहे. खुले व सुधृढ इंटरनेट जोपासणे आमचे ध्येय आहे.
+अधिक जाणून घ्या
]]>
+
+
+ गोपनीयता आणि सुरक्षा
+
+
+ ट्रॅकिंग, कुकीज, डाटा पर्याय
+
+
+ पूर्वनिर्धारित ठरवा, ऑटोकंप्लिट
+
+
+
+
+ %1$s बद्दल, मदत
+
+
+ वेब मजकूर
+
+
+ अँप बदलताना
+
+
+ सर्वसाधारण
+
+
+ माहिती संकलन आणि वापर
+
+ शोधा
+
+
+ शोध सूचना मिळवा
+
+ आपण जे पत्ता पट्टी मध्ये टाईप कराल ते %1$s आपल्या शोध इंजिनला पाठवेल
+
+
+ पूर्वनिर्धारीत
+
+
+ शोध इंजिन
+
+
+ चालू
+
+
+ बंद
+
+
+ URL स्वयंपूर्ण
+
+
+ टॉप साईट करिता
+
+
+ आपण जोडलेल्या साईट करिता
+
+
+ साईट व्यवस्थापित करा
+
+
+ साईट व्यवस्थापित करा
+
+
+ + सानुकूल URL जोडा
+
+
+ आपली स्वयंपूर्णता यादी:
+
+
+ URL जोडा
+
+
+ सानुकूल URL जोडा
+
+
+ सानुकूल URL जोडा
+
+
+ स्वयंपूर्ण करण्यासाठी दुवा जोडा
+
+
+ कूकीज आणि संकेतस्थळाची माहिती
+
+
+ माहिती पर्याय
+
+
+ सानुकूल URLs काढा
+
+
+ अधिक जाणा
+
+
+ सानुकूल स्वयंपूर्ण URLs जोडा आणि व्यवस्थापित करा.
+
+
+ जोडायची URL
+
+
+ चिकटवा किंवा URL प्रविष्ट करा
+
+
+ उदाहरण: mozilla.org
+
+
+ उदाहरण: example.com
+
+
+ नवीन सानुकूल URL समाविष्ट केली.
+
+
+ काढून टाका
+
+
+ काढून टाका
+
+
+ आपण प्रविष्ट केलेली URL दोनदा तपासा.
+
+ भाषा
+
+ पूर्वनिर्धारीत प्रणाली
+
+ गुप्तता
+ जाहिरात मागोवा यंत्रणा अवरोधित करा
+ काही जाहिराती संकेतस्थळावरील भेटीची नोंद करून ठेवतात, जरी तुम्ही जाहिरातीवर क्लिक केले नाही तरी
+ विश्लेषणात्मक ट्रॅकर्स अवरोधित करा
+ टॅपिंग आणि स्क्रोलिंगसारख्या क्रियाकलाप एकत्रित करण्यासाठी, मोजमापआणि विश्लेषण करण्यासाठी वापरला जातो
+ सामाजिक ट्रॅकर अवरोधित करा
+ आपल्या भेटींचा मागोवा घेण्यासाठी आणि शेअर बटन सारखी सुविधा दर्शवण्यासाठी साईट वर ठेवले आहे
+ इतर मजकूर ट्रॅकर्स अवरोधित करा
+ सक्षम करण्यामुळे काही पृष्ठ अनपेक्षितरित्या वागू शकतात
+ कुकीज अवरोधित करा
+
+
+ नाही धन्यवाद
+ केवळ तृतीय-पक्ष ट्रॅकर कुकीज अवरोधित करा
+ केवळ तृतीय-पक्षाच्या कुकीज अवरोधित करा
+ कृपया हो
+
+
+ अँप उघडण्यासाठी बोटाचा ठसा वापरा
+
+
+ स्टेल्थ
+
+ अँप बदलताना वेब पृष्ठे लपवा आणि स्क्रीनशॉट घेणे आडवा.
+
+ सुरक्षा
+
+ कार्यक्षमता
+ वेब फॉन्ट अवरोधित करा
+
+ याचा परिणाम हरवलेल्या प्रतिमा किंवा चिन्हांमध्ये होऊ शकतो
+
+ JavaScript अवरोधित करा
+
+ पृष्ठे जलद लोड होतील पण अनपेक्षित वर्तणूक दाखवू शकतात
+
+
+ मुलभूत ब्राउझर म्हणून %1$s वापरा
+
+ Mozilla
+ वापर डेटा पाठवा
+
+
+ अधिक जाणा
+
+
+ सर्वांसाठी %1$s उपलब्ध करण्यासाठी व सुधारण्यास जी गरजेची आहे फक्त तीच माहिती जमा करण्यास Mozilla तत्पर आहे.
+
+
+ गोपनीयता सूचना
+
+
+ %1$s विषयी
+
+
+ स्थापित केलेले शोध इंजिन
+
+
+ शोध इंजिन निवडा
+
+
+ डीफॉल्ट शोध इंजिन पुनर्संचयित करा
+
+
+ + अजून एक शोध इंजिन जोडा
+ शोध इंजिने काढून टाका
+ काढून टाका
+
+ अजून एक शोध इंजिन जोडा
+
+ आपले पसंतीचे इंजिन निवडा:
+
+
+ शोध इंजीन जोडा
+
+ शोध इंजिनचे नाव
+ शोधण्यास वापरायची अक्षरमाळ
+ जतन करा
+
+
+ उदाहरण: example.com/search/?q=%s
+
+ नवीन शोध इंजिन जोडले.
+
+ शोध इंजिनाचे नाव प्रविष्ट करा
+ एक स्थापित शोध इंजिन आधीपासून त्या नावाचा वापर करीत आहे.
+
+ सर्च स्ट्रिंग प्रविष्ट करा
+
+ शोध अक्षरमाळ उदाहरणाच्या स्वरूपात आहे का ते तपासा
+
+
+ इनपुट साफ करा
+
+
+ बरखास्त करा
+
+
+ ब्राऊझिंग इतिहास खोडून टाका
+
+
+ उघडलेले टॅब: %1$s
+
+
+ सुरक्षित कनेक्शन
+
+
+ लोड करत आहे
+
+
+ वेबसाइट लोड केली
+
+
+ आणखी पर्याय
+
+
+ अधिक पर्याय बटण
+
+
+ पुढे नेव्हिगेट करा
+
+
+ वेबसाइट रीलोड करा
+
+
+ मागे नॅव्हिगेट करा
+
+
+ वेबसाइट लोड करणे थांबवा
+
+
+ मागील अॅप वर परत या
+
+
+ अवरोधित मागोवा यंत्रणा संख्या
+
+
+ मागोवा यंत्रणा अवरोधित करा
+
+ आपले हक्क
+
+ दुसर्या अॅपमध्ये दुवा उघडा
+
+ दुआ %2$s मध्ये उघडण्यासाठी आपण %1$s सोडू शकता.
+
+ दुवा उघडू शकणारा अॅप शोधा
+
+ आपल्या उपकरणातील कोणतीही अँप ही दुवा उघडू शकत नाही. ही दुवा उघडू शकणारी अँप %2$s वर शोधण्यासाठी आपण %1$s सोडून जाऊ शकता.
+
+ खाजगी ब्राउझिंगच्या बाहेर पडायचे आहे का?
+
+
+ %1$s समाप्त
+
+
+ उघडा
+
+
+
+
+
+
+
+
+
+ सर्व्हर आढळला नाही
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ आपली गोपनीयता वाढवा
+
+ खाजगी ब्राऊझिंग पुढच्या स्तरावर न्या. जाहिराती आणि इतर मजकूर अवरोधित करा जो आपला विविध साईट वरून मागोवा घेऊ शकतो आणि पृष्ठ लोड वेळ कमी करू शकतो.
+
+
+ आपला शोध, आपल्या पद्धतीने
+
+ काही वेगळे शोधत आहात का? सेटिंग मधून अन्य पूर्वनिर्धारित शोध इंजिन ठरवा.
+
+
+ आपल्या मुख्य पटलावर शॉर्टकट जोडा
+
+ %1$s मध्ये आपल्या आवडत्या साईट्स वर त्वरित परत जा. फक्त %1$s मेनू मधुन \"मुख्य पृष्ठात जोडा\" निवडा.
+
+
+ गोपनीयता एक सवय बनवा
+
+ %1$s हा आपला पूर्वनिर्धारित ब्राउझर म्हणून निवडा आणि इतर अँप मधून वेब पृष्ठ उघडताना खाजगी ब्राऊझिंग चा फायदा घ्या.
+
+ ठीक आहे, समजले!
+ वगळा
+ पुढे
+
+
+ -
+
+
+ जोडा
+
+ हो
+
+
+ रद्द करा
+
+ नाही
+
+
+ खाजगी ब्राऊझिंग सत्र
+
+
+ सूचनेवरून आपले %1$s सत्र एका टॅप मध्ये नष्ट करता येते. आपल्या ब्राउझर मध्ये काय चालू आहे हे पाहायला किंवा अँप उघडावे लागत नाही.
+
+
+ ब्राऊझिंग इतिहास नष्ट करा
+
+
+ Firefox डाउनलोड करा
+
+
+
+
+
+
+
+
+ Mozilla Public License आणि इतर खुल्या स्त्रोत परवान्यांच्या नियमांतर्गत उपलब्ध करून दिले गेले आहे.]]>
+
+
+ इथे मिळू शकते.]]>
+
+
+ परवान्यांतर्गत उपलब्ध आहे.]]>
+
+
+ GNU General Public License v3 अंतर्गत एक विभक्त आणि स्वतंत्र संसाधन म्हणून %1$s वापरते, ज्या इथे उपलब्ध आहेत.]]>
+
+
+ वापरकर्त्याचे नाव
+ पासवर्ड
+ पुसा
+
+
+
+ सुरक्षित जोडणी
+ असुरक्षित जोडणी
+
+ %1$s द्वारे तपसालेले
+
+
+ साइट सुरक्षा
+ URL आधिपासूनच अस्तित्वात आहे.
+
+
+ पृष्ठामध्ये शोधा
+
+
+ पृष्ठामध्ये शोधा
+
+
+ %1$d/%2$d
+
+ %2$d पैकी %1$d
+
+
+ पुढील परिणाम शोधा
+
+ मागील परिणाम शोधा
+
+ पृष्ठावर शोधणे रद्द करा
+
+
+
+
+ डेस्कटॉप साइटची विनंती करा
+
+
+ डेस्कटॉप साइट
+
+
+ URL ची प्रत बनवली
+
+
+ डेव्हलपर साधने
+
+
+ प्रगत
+
+
+ Wi-Fi द्वारे दूरस्थ डीबगिंग
+
+
+ नवीन सत्रात दुवा उघडा
+
+
+ बोटाच्या ठश्याचे चिन्ह
+
+
+ बोटाचा ठसा ओळखला नाही. पुन्हा प्रयत्न करा.
+
+
+ बोट पटकन हलवले. पुन्हा प्रयत्न करा.
+
+
+ शोध सूचना दाखवायच्या?
+
+
+ सूचना मिळवण्यासाठी, %1$s ला आपण निवडलेल्या शोध इंजिनला आपण पत्ता पट्टीमध्ये काय टाईप करता ते पाठवावे लागेल.
+
+
+ नाही
+
+
+ होय
+
+
+ काही शोध इंजिन सूचना दाखवू शकत नाहीत.
+
+
+ रद्द करा
+
+
+
+
+ साइट अनपेक्षितरित्या वागत आहे?\n ट्रॅकिंग संरक्षण बंद करण्याचा प्रयत्न करा
+
+
+ मुख्य पृष्ठावर जोडा]]>
+
+
+ प्रत्येक दुवा %1$s मध्ये उघडा\n%1$s ला पूर्वनिर्धारित ब्राउझर करा
+
+
+ सर्वात जास्त वापरलेल्या साईट च्या URL स्वयंपूर्ण करा\nकोणतीही URL पत्ता पट्टी मध्ये दीर्घकाळ दाबून धरा
+
+
+ एका नवीन टॅबमध्ये एक दुवा उघडा\n एका पृष्ठावर कोणताही दुवा दीर्घ-दाबून ठेवा
+
+
+ आरंभ पटलावर सूचना बंद करा
+
+
+ नवीन टॅब उघडले
+
+
+ स्विच करा
+
+
+ नवीन टॅबमध्ये दुवा त्वरित स्विच करा
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ms/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000000..9a83b39902
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ms/strings.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+ Batal
+ OK
+
+ Simpan
+
+ Cari atau masukkan alamat
+
+ Pelayaran peribadi automatik.\nLayar. Buang. Ulang.
+
+ Sejarah pelayaran anda telah dibuang.
+
+ Sejarah pelayaran tab telah dibuang.
+
+ Cari %1$s
+
+ Kongsi…
+
+ Laporkan Isu Laman
+
+ Buka dalam %1$s
+
+ Buka dalam…
+
+ Tambah ke Skrin Halaman utama
+
+ Tetapan
+ Perihal
+ Bantuan
+ Hak Anda
+
+ Penjejak disekat
+
+ Menyahaktifkan ini boleh membetulkan beberapa masalah laman
+
+ Sekatan Kandungan
+ Nyahaktifkan untuk membetulkan beberapa laman
+
+ Dikuasakan oleh %1$s
+
+ Kongsi via
+
+ Buang sejarah pelayaran
+
+ Buka
+
+ Buang dan Buka
+
+ Buang
+
+ Buang sejarah pelayaran
+
+
+ Buang & buka
+
+ Buang dan buka %1$s
+
+ Privasi & Keselamatan
+
+ Penjejakan, kuki, pilihan data
+
+ Tetapkan piawai, autolengkap
+
+ Perihal %1$s, bantuan
+
+ Kandungan Web
+
+ Aplikasi Penukaran
+
+ Umum
+
+ Pengumpulan Data & Penggunaan
+
+ Carian
+
+ Dapatkan cadangan carian
+ %1$s akan menghantar apa yang anda taip dalam bar alamat kepada enjin carian
+
+ Piawai
+
+ Enjin carian
+
+ Aktif
+
+ Nyahaktif
+
+ Auto-lengkap URL
+
+ Untuk Laman Teratas
+
+ Untuk Laman Yang Anda Tambah
+
+ Urus laman
+
+ Urus laman
+
+ + Tambah URL penyesuaian
+
+ Tambah URL penyesuaian
+
+ Tambah URL penyesuaian
+
+ Tambah pautan untuk autolengkap
+
+ Kuki dan Data Laman
+
+ Pilihan Data
+
+ Buang URLs penyesuaian
+
+ Ketahui selanjutnya
+
+ Tambah dan urus penyesuaian URL auto-lengkap.
+
+ URL untuk ditambah
+
+ Tampal atau masukkan URL
+
+ Contoh: mozilla.org
+
+ Contoh: example.com
+
+ Penyesuaian URL baru ditambah.
+
+ Buang
+
+ Buang
+
+ Semak semula URL yang anda masukkan.
+
+ Bahasa
+ Piawai sistem
+
+ Privasi
+ Sekat penjejak iklan
+ Sesetengah iklan menjejaki lawatan anda dalam laman, walaupun anda tidak klik iklan
+ Sekat penjejak analitik
+ Digunakan untuk mengumpul, menganalisis dan menilai aktiviti, seperti klik dan skrol
+ Sekat penjejak sosial
+ Disisipkan di dalam laman untuk menjejaki lawatan anda dan memaparkan fungsi seperti butang kongsi
+ Sekat penjejak kandungan lain
+ Sesetengah halaman mungkin tidak berfungsi dengan sempurna jika ciri ini diaktifkan
+ Sekat kuki
+
+ Sekat kuki penjejak pihak ke-3 sahaja
+ Sekat kuki pihak ke-3 sahaja
+
+ Guna cap jari untuk membuka aplikasi
+
+ Halimunan
+ Sorok halaman web apabila bertukar aplikasi dan sekat mengambil skrinsyot.
+
+ Keselamatan
+
+ Prestasi
+ Sekat fon Web
+ Boleh menyebabkan imej atau ikon hilang
+
+ Sekat JavaScript
+ Halaman dimuatkan lebih cepat, tetapi mungkin ada yang tidak dijangka
+
+ Jadikan %1$s pelayar piawai
+
+ Mozilla
+ Hantar data penggunaan
+
+ Ketahui selanjutnya
+
+ Mozilla mengumpulkan hanya yang diperlukan untuk menyedia dan meningkatkan %1$s.
+
+ Notis Privasi
+
+ Perihal %1$s
+
+ Enjin carian dipasang
+
+ Pulih enjin carian piawai
+
+ + Tambah enjin carian lain
+ Buang enjin carian
+ Buang
+
+ Tambah enjin carian
+
+ Nama enjin carian
+ Cari string yang mahu digunakan
+ Simpan
+
+ Contoh: example.com/search/?q=%s
+
+ Enjin carian baru sudah ditambah.
+
+ Masukkan nama enjin carian
+ Enjin carian yang dipasang sudah menggunakan nama itu.
+
+ Masukkan string carian
+
+ Pastikan string carian sepadan format contoh
+
+ Hapuskan input
+
+ Abai
+
+ Buang sejarah pelayaran
+
+ Tab terbuka: %1$s
+
+ Sambungan selamat
+
+ Memuatkan
+
+ Laman web dimuatkan
+
+ Pilihan lain
+
+ Butang pilihan lain
+
+ Navigasi depan
+
+ Muat semula laman web
+
+ Navigasi undur
+
+ Hentikan memuat laman web
+
+ Kembali ke aplikasi dahulu
+
+ Bilangan penjejak disekat
+
+ Sekat penjejak
+
+ Hak Anda
+
+ Buka pautan guna aplikasi lain
+ Anda boleh keluar dari %1$s untuk membuka pautan ini dalam %2$s.
+ Cari aplikasi yang boleh buka pautan
+ Tiada aplikasi pada peranti anda dapat membuka pautan ini. Anda boleh tinggalkan %1$s untuk mencari %2$s aplikasi yang boleh melaksanakannya.
+ Tamatkan Pelayaran Peribadi?
+
+ %1$s sudah siap
+
+ Buka
+
+
+
+
+
+
+
+
+
+ Pelayan tidak ditemui
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tingkatkan privasi anda
+ Membawa pelayaran peribadi ke tahap seterusnya, Sekat iklan dan kandungan lain yang menjejaki anda di serata laman dan melambatkan tempoh memuatkan halaman.
+
+ Carian anda, cara anda
+ Mencari sesuatu yang berlainan? Pilih enjin piawai lain dalam Tetapan.
+
+ Letakkan pintasan dalam skrin utama
+ Kembali ke laman kegemaran anda dalam %1$s dengan pantas. Hanya pilih \"Tambah ke skrin Utama\" dari dalam menu %1$s.
+
+ Jadikan privasi satu tabiat
+ Tetapkan %1$s sebagai pelayar piawai anda dan dapatkan manfaat daripada pelayaran peribadi apabila anda buka pautan halaman web dari aplikasi lain.
+
+ OK, faham!
+ Langkau
+ Seterusnya
+
+ -
+
+ Tambah
+
+ Batal
+
+ Sesi pelayaran peribadi
+
+ Anda boleh buang sesi %1$s dengan menekan notifikasi. Tidak perlu buka aplikasi atau lihat kandungan dalam pelayar.
+
+ Buang sejarah pelayaran
+
+ Muat turun Firefox
+
+ %1$s adalah perisian percuma dan sumber terbuka yang dihasilkan oleh Mozilla dan para penyumbang lain.
+
+
+
+
+ Nama pengguna
+ Kata laluan
+ Buang
+
+ Sambungan Selamat
+ Sambungan Tidak Selamat
+ Disahkan oleh: %1$s
+
+ Keselamatan Laman
+ URL sudah wujud
+
+ Cari dalam Halaman
+
+ Cari dalam halaman
+
+ %1$d/%2$d
+ %1$d daripada %2$d
+
+ Cari hasil seterusnya
+ Cari hasil dahulu
+ Batal cari dalam halaman
+
+
+
+ Pinta laman desktop
+
+ URL disalin
+
+ Alatan pembangun
+
+ Lanjutan
+
+ Nyahpepijat jauh via USB/Wi-Fi
+
+ Ikon cap jari
+
+ Cap jari tidak dikenali. Cuba lagi.
+
+ Jari digerak terlalu pantas. Cuba lagi.
+
+ Tidak
+
+ Ya
+
+ Sesetengah enjin carian tidak dapat menunjukkan cadangan.
+
+ Abai
+
+
+
+ Laman berkelakuan tidak dijangka?\n Cuba nyahaktifkan Perlindungan Penjejakan
+
+ Dapatkan akses sekali-sentuh ke laman yang kerap dilawati%1$s Menu > Tambah ke skrin Utama
+
+ Buka pautan dalam %1$s\n Tetapkan %1$s sebagai pelayar piawai
+
+ Autolengkap URL untuk laman yang kerap dilawati\n Tekan lama-lama URL dalam bar alamat
+
+ Buka pautan dalam tab baru\n Tekan lama-lama pautan atau halaman
+
+ Nyahaktifkan panduan pada skrin mula
+
+ Tab baru dibuka
+
+ Tukar
+
+ Tukar ke pautan dalam tab baharu dengan segera
+
+ Sekat laman yang berpotensi berbahaya dan menipu
+ Sekat laman yang dilaporkan menipu dan menyerang, laman perisian hasad dan laman perisian yang tidak diingini.
+
+ Pengecualian
+ Anda telah nyahaktifkan Sekatan Kandungan untuk laman web ini.
+ Buang
+ Buang semua laman web
+
+ Tab Ranap
+ Maaf. Kami ada masalah dengan tab ini.
+ Sebagai pelayar peribadi, kami tidak pernah menyimpan dan tidak boleh memulihkan tab ini.
+ Tutup Tab
+
+
+
+
+ Hantar laporan ranap ke Mozilla
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-my/strings.xml b/mobile/android/focus-android/app/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000000..6d7813f453
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-my/strings.xml
@@ -0,0 +1,634 @@
+
+
+
+
+
+
+
+
+ မလုပ်ဆောင်တော့ပါ
+
+ ကောင်းပြီ
+
+ သိမ်းရန်
+
+
+ ရှာဖွေပါ သို့မဟုတ် လိပ်စာရိုက်ပါ
+
+ အလိုအလျောက် တစ်ကိုယ်ရေသုံး၊ \nသုံးပြီးဖျက်
+
+
+ သင့် ဘယောက်ဆာသုံး မှတ်တမ်းဖျက်ပြီး
+
+
+ ကြည့်ရှုမှတ်တမ်းကို ရှင်းလင်းထားသည်။
+
+
+ %1$s ကိုရှာမယ်
+
+
+ မျှဝေပါ…
+
+
+ ဆိုက်ပြဿနာကို သတင်းပို့ရန်
+
+
+ %1$s တွင်ဖွင့်ပါ
+
+
+ …တွင် ဖွင့်ရန်
+
+
+ ပင်မစာမျက်နှာသို့ ထည့်ပါ
+
+ အပြင်အဆင်များ
+ အကြောင်း
+ အကူအညီ
+ သင်၏ အခွင့်အရေးများ
+
+
+ နောက်ယောင်ခံများကို တားဆီးထား
+
+
+ ယခုလုပ်ဆောင်ချက်ကို ပိတ်ခြင်းသည် အချို့သော ဝဘ်ဆိုက်များကို ကောင်းမွန်စွာ ဆောင်ရွက်နိုင်စေသည်
+
+
+ အကြောင်းအရာ ပိတ်ပင်ခြင်း
+
+ အချို့သောဝဘ်ဆိုက်များအတွက် ပိတ်ပါ
+
+
+ %1$s ထောက်ပံ့ပေးထားတယ်
+
+
+ မျှဝေရန်
+
+
+ ကြည့်ရှုမှုမှတ်တမ်းကို ရှင်းလင်းရန်
+
+
+ ဖွင့်ပါ
+
+
+ ဖျက်ပြီး ဖွင့်ပါ
+
+
+ ရှင်းလင်းရန်
+
+
+ ကြည့်ရှုမှုမှတ်တမ်းကို ရှင်းလင်းရန်
+
+
+
+ ဖျက်ပြီးဖွင့်ပါ
+
+
+ %1$s ကို ဖျက်ပြီး ဖွင့်ပါ
+
+
+
+ ကိုယ်ရေးကာကွယ်မှုနှင့် လုံခြုံရေး
+
+
+ ကွတ်ကီးများ ခြေရာခံခြင်း နှင့် ဒေတာအသုံးပြုခြင်းဆိုင်ရာ ရွေးချယ်မှုများ
+
+
+ ပုံသေထား၊ အလိုအလျောက်ဖြည့်စွက်
+
+
+
+
+ %1$s အကြာင်း၊ ကူညီမှု
+
+
+ ဝတ်ဘ်စာကိုယ်
+
+
+ အက်ပ်များ ဖွင့်ခြင်း
+
+
+ အထွေထွေ
+
+
+ ဒေတာစုစည်းမှုနှင့်အသုံးပြုမှု
+
+ ရှာဖွေရန်
+
+
+ ရှာဖွေရေး အကြံပေးချက်များ ရယူရန်
+
+ လိပ်စာဘားတန်းတွင် ရိုက်သမျှကို %1$s သည် ရှာဖွေရေးယန္တရားသို့ ပေးပို့ပါမည်။
+
+
+ မူလသတ်မှတ်ချက်
+
+
+ ရှာဖွေရေး ယန္တရား
+
+
+ ဖွင့်ပါ
+
+
+ ပိတ်ပါ
+
+
+ URL အလိုအလျောက်ဖြည့်စွက်
+
+
+ ထိပ်တန်းဆိုက်များအတွက်
+
+
+ သင်ရွေးချယ်ထားသော ဝဘ်ဆိုက်များအတွက်
+
+
+ ဝဘ်ဆိုက်များကို စီမံရန်
+
+
+ ဝဘ်ဆိုက်များကို စီမံရန်
+
+
+ + စိတ်ကြိုက် URLs တိုးရန်
+
+
+ စိတ်ကြိုက် URLs တိုးရန်
+
+
+ စိတ်ကြိုက် URLs တိုးရန်
+
+
+ အလိုအလျက်ဖြည့်ရန် လင့်ကို ထည့်ပေးပါ
+
+
+ ကွတ်ကီးများနှင့် ဝဘ်ဆိုက် အချက်အလက်
+
+
+ ဒေတာအသုံးပြုရန် ရွေးချယ်မှုများ
+
+
+ စိတ်ကြိုက် URLs ဖယ်ရှားရန်
+
+
+ ပိုမိုလေ့လာရန်
+
+
+ URLs ကို စိတ်ကြိုက် ပေါင်းထည့် စီမံပါ
+
+
+ တိုးမဲ့ URL
+
+
+ URL ကို ကူးယူ သို့မဟုတ် ဝင်ပါ
+
+
+ Example: mozilla.org
+
+
+ Example: example.com
+
+
+ စိတ်ကြိုက်အသစ် URL ပေါင်းထည့်ခဲ့သည်
+
+
+ ဖယ်ရှားပါ
+
+
+ ဖယ်ရှားပါ
+
+
+ ဝင်ထားသော URL ကို ပြန်လည်စစ်ဆေးပါ
+
+ ဘာသာစကား
+
+ စနစ် သတ်မှတ်ချက်
+
+ ကိုယ်ပိုင်လွတ်လပ်ခွင့်
+ ကြော်ငြာနောက်ယောက်ခံ ကိရိယာများကို ပိတ်ပင်ပါ
+ အချို့သော ကြော်ငြာများသည် ၎င်းတို့ကို ကလစ်နှိပ်ခြင်း မပြုလုပ်ပါပဲနှင့် ဆိုက်များကြည့်ရှုခြင်းကို မှတ်သားသည်။
+ သရုပ်ခွဲလေ့လာသည့် နောက်ယောင်ခံကိရိယာများကို ပိတ်ပင်ပါ
+ တို့ထိခြင်း၊ အထက်/အောက်ရွှေ့ခြင်း စသည့် လှုပ်ရှားမှုများကို စုဆောင်းရန်၊ ပိုင်းခြားစိတ်ဖြာရန်နှင့် တိုင်းတာရန်အတွက် အသုံးပြုသည်။
+ လူမှုမီဒီယာ နောက်ယောင်ခံကိရိယာများကို ပိတ်ပင်ပါ
+ ဝဘ်ဆိုက်များတွင် မြှုပ်ထည့်ထားခြင်းသည် လည်ပတ်ခြင်းကို မှတ်သားရန်နှင့် မျှဝေခလုတ်များကဲသို့ လုပ်ဆောင်မှုများကို ပြသရန်အတွက် ဖြစ်သည်။
+ အခြားသော အကြောင်းအရာနောက်ယောက်ခံကိရိယာများကို ပိတ်ပင်ပါ
+ လုပ်ဆောင်ချက်ကို ဖွင့်လိုက်ခြင်းသည် အချို့သော ဝဘ်စာမျက်နှာများ၏ လုပ်ဆောင်ချက်များကို ထိခိုက်စေနိုင်သည်။
+ ကွတ်ကီးများကို ဖျောက်ထားပါ
+
+ အခြားသူများ၏ နောက်ယောင်ခံ ကိရိယာများကိုသာ တားဆီးပါ
+ ပြင်ပကွက်ကီးများကို မသိမ်းပါနှင့်
+
+
+ အက်ပ်ကို ဖွင့်ရန် လက်ဗွေကို အသုံးပြုပါ
+
+
+ တိတ်တဆိတ်အသုံးပြုမှု
+
+ အက်ပ်များကို ပြောင်းလဲအသုံးပြုနေစဉ် ဝဘ်စာမျက်နှာများကို မပြပါနှင့်။ ထို့ပြင် မျက်နှာပြင်ပုံရိပ်ဖမ်းခြင်းကို တားဆီးပါ။
+
+ လုံခြုံရေး
+
+ လုပ်ဆောင်မှု
+ ဝဘ်ဖောင့်များကို ပိတ်ပင်ပါ
+
+ အိုင်ကွန်များ သို့မဟုတ် ရုပ်ပုံများမပေါ်ခြင်း ဖြစ်စေနိုင်သည်
+
+ JavaScript ကိုပိတ်ဆို့ရန်
+
+ စာမျက်နှာများ မြန်ဆန်စွာ ဆွဲတင်သော်လည်း မှန်ကန်စွာ အလုပ်မလုပ်ပါ
+
+
+ %1$s ကို ပုံသေအသုံးပြုမည့် ဘရောင်ဇာအဖြစ် သတ်မှတ်ပါ
+
+ Mozilla
+ အသုံးပြုမှုဆိုင်ရာ အချက်အလက်များကို ပေးပို့ရန်
+
+
+ ပိုမိုလေ့လာရန်
+
+
+ Mozilla သည် လူတိုင်းအတွက် %1$s ကို ပေးစွမ်းရန်နှင့် တိုးတက်ကောင်းမွန်စေရန်အတွက်သာ ကောက်ခံမှုပြုခြင်း ဖြစ်ပါသည်။
+
+
+ ကိုယ်ရေးကာကွယ်မှု သတိပေးချက်
+
+
+ %1$s အကြောင်း
+
+
+ သွင်းထားသော ရှာဖွေအင်ဂျင်များ
+
+
+ ပုံသေ ရှာဖွေအင်ဂျင်များကို မူလအတိုင်း ပြန်သတ်မှတ်ပါ
+
+
+ + အခြားရှာဖွေအင်ဂျင်ကို ထပ်ထည့်ပါ
+ ရှာဖွေအင်ဂျင်များ ဖယ်ရှားပါ
+ ဖယ်ရှားပါ
+
+
+ ရှာဖွေရေး အင်ဂျင် တိုးရန်
+
+ အင်ဂျင်နာမည် ရှာဖွေပါ
+ အသုံးပြုရန် စာကြောင်းရှာဖွေပါ
+ သိမ်းဆည်းပါ
+
+
+ Example: example.com/search/?q=%s
+
+ ရှာဖွေအင်ဂျင်အသစ် ထည့်ခဲ့သည်
+
+ ရှာဖွေအင်ဂျင်နာမည် ဝင်ပါ
+ သွင်းပြီးရှာဖွေအင်ဂျင်ကို ဒီနာမည်ဖြင့် အသုံးပြုပြီးဖြစ်သည်
+
+ စာကြောင်းဖြင့် ရှာဖွေဝင်ရောက်ပါ
+
+ ရှာဖွေစာကြောင်း ဥပမာပုံစံဖြင့် ကိုက်ညီမှုရှိမရှိ စစ်ဆေးပါ
+
+
+ Input ကို ရှင်းလင်းရန်
+
+
+ ပိတ်ရန်
+
+
+ ကြည့်ရှုမှုမှတ်တမ်းကို ရှင်းလင်းရန်
+
+
+ ဖွင့်ထားသော တပ်ဗ်များ။ %1$s
+
+
+ လုံခြုံသော ချိတ်ဆက်မှု
+
+
+ ဆောင်ရွက်နေသည်
+
+
+ ဝဘ်ဆိုက်ကို ဖွင့်ပြီးပြီ
+
+
+ နောက်ထပ် ရွေးချယ်စရာများ
+
+
+ အခြားဆောင်ရွက်နိုင်သည်များ ခလုတ်
+
+
+ ရှေ့သို့ သွားပါ
+
+
+ ဝဘ်ဆိုက်ကို ပြန်ဖွင့်ပါ
+
+
+ နောက်သို့ သွားပါ
+
+
+ ဝဘ်ဆိုက်ဖွင့်ခြင်းကို ရပ်ပါ
+
+
+ အရင်ကအက်ပ်သို့ သွားပါ
+
+
+ တားဆီးထားသော ခြေရာခံကိရိယာ အရေအတွက်
+
+
+ နောက်ယောင်ခံများကို တားဆီးပါ
+
+ သင်၏ အခွင့်အရေးများ
+
+ အခြားအက်ပ်တွင် လင့်ခ်ကို ဖွင့်ပါ
+
+ %2$s တွင် ယခုလင့်ခ်ကို ဖွင့်ရန် %1$s ထံမှ ထွက်ခွာနိုင်ပါသည်။
+
+ လင့်ခ်ကို ဖွင့်နိုင်သော အက်ပ်ကို ရှာပါ
+
+ ယခုလင့်ခ်ကို ကိရိယာရှိ မည်သည့်အက်ပ်မျှ မဖွင့်နိုင်ပါ။ ၎င်းကို ဖွင့်နိုင်သည့်အက်ပ် %2$s တွင် ရှာဖွေရန် %1$s ထံမှ ထွက်ခွာနိုင်ပါသည်။
+
+ လုံခြုံစွာ ဝဘ်ဆိုက်ကြည့်ရှုခြင်းမှ ထွက်ပါမည်လား။
+
+
+ %1$s ကို ဆွဲယူခြင်း ပြီးဆုံးပါပြီ
+
+
+ ဖွင့်ပါ
+
+
+
+
+
+
+
+
+
+ ဆာဗာကို မတွေ့ပါ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ကိုယ်ရေးကာကွယ်မှုကို တိုးမြှင့်ပါ
+
+ သီးသန့်ကြည့်ရှုမှုကို ပိုမိုကောင်းမွန်စေပါ။ စာမျက်နှာဖွင့်ချိန်ကို ကြာမြင့်စေနိုင်သော ကြော်ငြာများနှင့် ဆိုက်များကတဆင့် နောက်ယောင်ခံနိုင်သော အကြောင်းအရာတို့ကို ပိတ်ပင်တားဆီးပါ။
+
+
+ ကိုယ့်နည်းကိုယ့်ဟန် အသုံးပြုမည်
+
+ အခြား မူသေရှာဖွေရေးယန္တရားကို အသုံးပြုလိုပါသလား။ အပြင်အဆင်များတွင် နှစ်သက်ရာကို ရွေးပါ။
+
+
+ အမြန်သုံးလင့်ခ်ကို မူလစာမျက်နှာသို့ ထည့်ပါ
+
+ %1$s တွင် သင်နှစ်သက်သော ဆိုက်များသို့ လျင်မြန်စွာ ပြန်လည်သွားရောက်နိုင်ပါသည်။ %1$s မီနူးထဲမှ \"မူလစာမျက်နှာသို့ ထည့်ပါ\" ကို ရွေးရန်သာ ဖြစ်သည်။
+
+
+ ကိုယ်ရေးလုံခြုံမှုကို နေ့စဉ် ဆောင်ရွက်ပါ
+
+ %1$s ကို မူသေဘရောင်ဇာအဖြစ် သတ်မှတ်ပါ။ ထို့ပြင် အခြားအက်ပ်များကနေ ဝဘ်စာမျက်နှာများ ကြည့်ရှုသည့်အခါ သီးသန့်လည်ပတ်ခြင်း၏ အကျိုးကျေးဇူးများကို ရယူအသုံးပြုပါ။
+
+ ကောင်းပြီ၊ ရပါပြီ။
+ ကျော်ရန်
+ ရှေ့သို့
+
+
+ -
+
+
+ ထည့်ရန်
+
+
+ မလုပ်ဆောင်တော့ပါ
+
+
+ သီးသန့်ကြည့်နေစဉ်ကာလ
+
+
+ အသိပေးချက်များသည် သင့် %1$s အသုံးပြုကာလကို တို့ထိရုံဖြင့် ဖယ်ရှားနိုင်စေသည်။ အက်ပ်ကို ဖွင့်ရန် သို့မဟုတ် ဘရောင်ဇာထဲတွင် ဖွင့်ထားသည်များကို ကြည့်ရန် မလိုအပ်ပါ။
+
+
+ လည်ပတ်မှတ်တမ်းကို ရှင်းလင်းရန်
+
+
+ Firefox ကို ဆွဲယူရန်
+
+
+
+
+
+
+
+
+ သုံးစွဲသူ အမည်
+ စကားဝှက်
+ ရှင်းလင်းပါ
+
+
+
+ လုံခြုံသော ချိတ်ဆက်မှု
+ မလုံခြုံသော ချိတ်ဆက်မှု
+
+ %1$s မှစစ်ဆေးပြီး
+
+
+ ဆိုက် လုံခြုံရေး
+ URL ရှိပြီးပြီ။
+
+
+ စာမျက်နှာထဲတွင် ရှာပါ
+
+
+ စာမျက်နှာထဲတွင် ရှာပါ
+
+
+ %1$d/%2$d
+
+ %2$d ရဲ့ %1$d
+
+
+ နောက်ထပ်ရလဒ် ရှာပါ
+
+ ပြီးခဲ့သည့်ရလဒ် ရှာပါ
+
+ စာမျက်နှာတွင် ရှာဖွေမှု ဖယ်ထုတ်ပါ
+
+
+
+
+ ဒတ်စ်တောခ့်ဆိုက်ကို တောင်းပါ
+
+
+ URL ကူးခဲ့သည်
+
+
+ ကျွမ်းကျင်သူသုံး ကိရိယာ
+
+
+ အဆင့်မြင့်
+
+
+ USB/Wi-Fi မှတဆင့် Remote debugging
+
+
+ လက်ဗွေပုံငယ်
+
+
+ လက်ဗွေကို မသိပါ။ ထပ်မံ ကြိုးစားကြည့်ပါ။
+
+
+ လက်ချောင်းရွှေ့ခြင်း မြန်လွန်းသည်။ ထပ်မံ ကြိုးစားကြည့်ပါ။
+
+
+ မလုပ်တော့ပါ
+
+
+ လုပ်မည်
+
+
+ အချို့သော ရှာဖွေရေးအင်ဂျင်များသည် အကြံပေးချက်များကို မပြသပေးနိုင်ပါ။
+
+
+ ဖြုတ်ရန်
+
+
+
+
+ ဝဘ်ဆိုက်သည် မူမမှန်ဖြစ်နေပါသလား။\n ခြေရာခံကာကွယ်မှုစနစ်ကို ပိတ်ပြီး ပြန်စမ်းကြည့်ပါ။
+
+
+ စတင်အသုံးပြုရာ ဖန်သားပြင်သို့ ပို့ပါ]]>
+
+
+ %1$s ရှိ လင့်ခ်တိုင်းကို ဖွင့်ပါ\n %1$s ကို မူသေဘရောက်ဇာအဖြစ် သတ်မှတ်ပါ
+
+
+ သင်အသုံးပြုများသော ဆိုက်များ၏ URLs ကို အလိုအလျောက်ဖြည့်ပါမည်\n လိပ်စာဘားတန်းထဲတွင် မည်သည့် URL ကိုမဆို ကြာကြာဖိပြီး ဖွင့်နိုင်သည်
+
+
+ လင့်ခ်ကို တဗ်ပ်အသစ်တွင် ဖွင့်ပါ\n စာမျက်နှာရှိ မည်သည့်လင့်ခ်တွင်မဆို ကြာကြာဖိပြီး ဖွင့်နိုင်သည်
+
+
+ စတင်အသုံးရန် မျက်နှာပြင်တွင် အကြံပြုချက်များ ပြသခြင်းကို ပိတ်ပါ
+
+
+ တပ်ဗ်အသစ် ဖွင့်ထားသည်
+
+
+ ပြောင်းလဲအသုံးပြုရန်
+
+
+ လင့်ခ်ကို ချက်ခြင်း တပ်ဗ်အသစ်ထဲတွင် အသုံးပြုရန်
+
+
+ အန္တရာယ်ရှိပြီး လှည့်ဖြားတတ်သည့် ဝဘ်ဆိုက်များကို တားဆီးပါ
+
+ အစီရင်ခံစာတင်ထားသော တိုက်ခိုက်မှုပြုတတ်သည့်ဆိုက်များကို ဖျောက်ပါ
+
+
+ ခြွင်းချက်များ
+
+ သင်သည် ထိုဝဘ်ဆိုက်များအတွက် Content Blocking ကို ပိတ်ထားသည်။
+
+ ဖယ်ရှားပါ
+
+ ဝဘ်ဆိုက်အားလုံးကို ဖယ်ရှားပါ
+
+
+ တပ်ဗ် ရပ်ဆိုင်းသွားသည်
+
+ ဝမ်းနည်းပါတယ်။ ယခုတပ်ဗ်တွင် ပြဿနာတစ်ခု ရှိနေပါသည်။
+
+ တစ်ကိုယ်ရေသုံး ဘရောင်ဇာတစ်ခု ဖြစ်သည့်အတွက် ယခုတပ်ဗ်ဆိုင်ရာ မှတ်တမ်းများကို မသိမ်းပါ၊ ထို့ပြင် ၎င်းတပ်ဗ်ကို ပြန်၍ဖွင့်မရနိုင်ပါ။
+
+ တပ်ဗ်ကို ပိတ်ပါ
+
+
+
+
+
+ Mozillaသို့ ပျက်စီးမှုအစီရင်ခံစာပို့ပါ
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-nb-rNO/strings.xml b/mobile/android/focus-android/app/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 0000000000..ad4b98e217
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,1117 @@
+
+
+
+
+
+
+
+
+ Avbryt
+
+ OK
+
+ Lagre
+
+
+ Søk eller oppgi adresse
+
+ Automatisk privat nettlesing.\nSurf. Slett. Gjenta.
+
+
+ Din nettleserhistorikk har blitt slettet.
+ Nettleserhistorikk er slettet
+
+
+ Fanens nettleserhistorikk har blitt slettet.
+
+
+ Søk etter %1$s
+
+
+ Del …
+
+
+ Rapporter problem med nettsted
+
+
+ Åpne i %1$s
+
+
+ Åpne i…
+
+
+ Legg til på startskjermen
+
+
+ Legg til i snarveier
+
+ Fjern fra snarveier
+
+
+ Innstillinger
+ Om
+ Hjelp
+ Dine rettigheter
+
+
+ Sporere blokkert
+
+
+ Hvis du slår av dette, kan det løse noen problemer med nettsiden
+
+
+ Innholdsblokkering
+
+ Slå av for å fikse noen nettsteder
+
+
+ Drevet av %1$s
+
+
+ Del via
+
+ Slett nettleserhistorikk?
+ Trykk eller fjern dette varselet for å slette nettleserhistorikken din på en sikker måte.
+
+
+ Trykk eller sveip dette varselet for å slette nettleserhistorikken din på en sikker måte.
+
+ Slett nettleserhistorikk
+
+
+ Åpne
+
+
+ Slett og åpne
+
+
+ Slett
+
+
+ Slett nettleserhistorikk
+
+
+
+ Slett og åpne
+
+
+ Slett og åpne %1$s
+
+
+
+ Søk med Focus
+
+ Søk med Klar
+
+ Søk med Focus Beta
+
+ Søk med Focus Nightly
+
+
+ %1$s gir deg kontroll.
+Bruk den som en privat nettleser:
+
+ Søk og surf rett i appen
+ Blokker sporere (eller oppdater innstillingene for å tillate sporere)
+ Slett for å fjerne infokapsler samt søk- og nettleserhistorikk
+
+
+%1$s er produsert av Mozilla. Vårt oppdrag er å fremme et sunt, åpent Internett.
+Les mer
]]>
+
+
+ Personvern og sikkerhet
+
+
+ Sporing, infokapsler, datavalg
+
+
+ Sett standard, autofullfør
+
+
+
+
+ Om %1$s, hjelp
+
+
+ Utvidet sporingsbeskyttelse
+
+
+ Nettinnhold
+
+
+ Bytte mellom apper
+
+
+ Generelt
+
+
+ Standard nettleser, språk
+
+
+ Datainnsamling og bruk
+
+ Søk
+
+
+ Få søkeforslag
+
+ %1$s sender det du skriver i adressefeltet til din søkemotor
+
+
+ Standard
+
+
+ Søkemotor
+
+
+ På
+
+
+ Av
+
+
+ Autofullfør nettadresse
+
+
+ For toppnettsteder
+
+
+ Aktiver at %s autofullfører over 450 populære nettadresser i adressefeltet.
+
+
+ For nettsteder du legger til
+
+
+ Aktiver for å få %s til å autofullføre dine foretrukne nettadresser.
+
+
+ Behandle nettsteder
+
+
+ Behandle nettsteder
+
+
+ + Legg til tilpasset nettadresse
+
+
+ Din liste over autofullføringer:
+
+
+ Legg til URL
+
+
+ Legg til tilpasset nettadresse
+
+
+ Legg til tilpasset nettadresse
+
+
+ Legg til lenke til autofullfør
+
+
+ Infokapsler og nettstedsdata
+
+
+ Datavalg
+
+
+ Fjern tilpassede nettadresser
+
+
+ Les mer
+
+
+ Legg til og administrer tilpassede nettadresser for autofullføring.
+
+
+ Nettadresse å legge til
+
+
+ Lim inn eller skriv inn nettadresse
+
+
+ Eksempel: mozilla.org
+
+
+ Eksempel: example.com
+
+
+ Ny tilpasset nettadresse lagt til.
+
+
+ Fjern
+
+
+ Fjern
+
+
+ Dobbeltsjekk nettadressen du skrev inn.
+
+ Språk
+
+ Systemstandard
+
+ Personvern
+ Blokker reklamesporere
+ Noen annonser sporer nettstedbesøk, selv om du ikke klikker på annonsene
+ Blokker analysesporere
+ Brukt for å samle inn, analysere og måle aktiviteter som tastetrykk og rulling
+ Blokker sosiale media-sporere
+ Innebygd på nettsteder for å spore besøkene dine og å vise funksjonalitet som dele-knapper
+ Blokker andre innholdssporere
+ Aktivering kan føre til at noen sider oppfører seg uventet
+ Blokker infokapsler
+
+
+ Nei takk
+ Blokker bare tredjepartssporingsinfokapsler
+ Blokker bare tredjepartsinfokapsler
+
+ Blokker infokapsler på tvers av nettsteder
+ Ja takk
+
+
+ Bruk fingeravtrykk til å låse opp appen
+
+
+ Lås opp med fingeravtrykk hvis du har lagt til snarveier eller når et nettsted allerede er åpent i %s.
+
+
+ Usynlig
+
+ Skjul nettsider når du bytter apper og unngå at det tas skjermbilder.
+
+ Sikkerhet
+
+ Ytelse
+ Blokker nettfonter
+
+ Kan resultere i at ikon eller bilder mangler
+
+ Blokker JavaScript
+
+ Sider kan lastes raskere, men kan også oppføre seg uventet
+
+
+ Bruk %1$s som standard nettleser
+
+ Mozilla
+ Send bruksdata
+
+
+ Les mer
+
+
+ Mozilla strever etter å bare samle inn det vi trenger for å tilby og forbedre %1$s for alle.
+
+
+ Personvernbestemmelser
+
+
+ Lisensinformasjon
+
+
+ Bibliotek som vi bruker
+
+
+ %s | OSS-bibliotek
+
+
+ Om %1$s
+
+
+ Installerte søkemotorer
+
+
+ Velg søkemotor
+
+
+ Gjenopprett standard søkemotorer
+
+
+ + Legg til en annen søkemotor
+ Fjern søkemotorer
+ Fjern
+
+
+ Legg til en annen søkemotor
+
+ Velg ønsket søkemotor:
+
+
+ Legg til søkemotor
+
+ Søkemotornavn
+ Søkestreng å bruke
+ Lagre
+
+
+ Eksempel: example.com/search/?q=%s
+
+ Ny søkemotor lagt til.
+
+ Skriv inn søkemotornavn
+ En installert søkemotor bruker allerede det navnet.
+
+ Skriv inn søkestreng
+
+ Kontroller at søkestrengen passer med eksempelformatet
+
+
+ Tøm skrivefeltet
+
+
+ Lukk
+
+
+ Slett nettleserhistorikk
+
+
+ Åpn faner: %1$s
+
+
+ Sikker tilkobling
+
+
+ Laster
+
+
+ Nettside lastet
+
+
+ Flere innstillinger
+
+
+ Knappen flere alternativer
+
+
+ Naviger fremover
+
+
+ Last siden på nytt
+
+
+ Naviger tilbake
+
+
+ Stopp lasting av nettside
+
+
+ Gå tilbake til forrige app
+
+
+ Antall blokkerte sporings-tjenester
+
+
+ Blokker sporere
+
+ Dine rettigheter
+
+ Åpne lenke i en annen app
+
+ Du kan gå ut av %1$s for å åpne denne lenken i %2$s.
+
+ Finn en app som kan åpne lenken
+
+ Ingen av appene på din enhet kan åpne denne lenken. Du kan gå ut av %1$s for å søke på %2$s etter en app som kan gjøre dette.
+
+ Avslutt privat nettlesing?
+
+
+ %1$s ferdig
+
+
+ Åpne
+
+
+
+
+
+
+
+
+
+
+ Lagt til i snarveier!
+
+ Fant ikke serveren
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lukk
+
+
+
+ Velkommen til %1$s
+
+
+ Rask. Privat. Ingen distraksjoner.
+
+
+ Kom i gang
+
+
+
+ %1$s er ikke som andre nettlesere
+
+
+ Vi sletter loggen din når du lukker appen for ekstra personvern.
+
+
+
+ Gjør %1$s til standard for å beskytte dine data med hver lenke du åpner.
+
+
+ Bruk som standard nettleser
+
+
+ Hopp over
+
+
+
+ Styrk personvernet ditt
+
+ Ta privat nettlesing til neste nivå. Blokker annonser og annet innhold som kan spore deg på tvers av nettsteder og som kan gjøre nedlasting av nettsider tregere.
+
+
+ Ditt søk på din måte
+
+ Søker du etter noe annet? Velg en annen standard søkemotor i innstillinger.
+
+
+ Legg til snarveier på startskjermen
+
+ Gå raskt tilbake til favorittnettstedene dine i %1$s. Velg «Legg til på startskjermen» fra %1$s-menyen.
+
+
+ Gjør personvern til en vane
+
+ Velg %1$s som standardnettleser, og få fordelene med privat nettlesing når du åpner nettsider fra andre apper.
+
+ OK, jeg forstår det!
+ Hopp over
+ Neste
+
+
+ -
+
+
+ Legg til
+
+
+ JA
+
+
+ Avbryt
+
+
+ NEI
+
+
+ Snarveien vil bli åpnet med utvidet sporingsbeskyttelse avslått
+
+
+ Privat nettlesingsøkt
+
+
+ Med varsel kan du slette økten din i %1$s med ett klikk. Du trenger ikke å åpne appen, eller å se hva som kjører i nettleseren din.
+
+
+ Slett nettleserhistorikk
+
+
+ Last ned Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License og andre lisenser for åpen kildekode.]]>
+
+
+ her.]]>
+
+
+ lisenser.]]>
+
+
+ GNU General Public License v3, og er tilgjengelig her .]]>
+
+
+ Brukernavn
+ Passord
+ Tøm
+
+
+
+ Sikker tilkobling
+ Usikker tilkobling
+
+ Bekreftet av: %1$s
+
+
+ Nettstedssikkerhet
+ URL-en finnes allerede
+
+
+ Søk på siden
+
+
+ Søk på siden
+
+
+ %1$d/%2$d
+
+ %1$d av %2$d
+
+
+ Søk etter neste
+
+ Søk etter forrige
+
+ Lukk søk på siden
+
+
+
+
+ Vis skrivebordsversjon
+
+
+ Datamaskinversjon
+
+
+ URL kopiert
+
+
+ Utviklerverktøy
+
+
+ Åpne lenker i apper
+
+
+ Avansert
+
+
+ Nettstedstillatelser
+
+
+ Redusering av infokapselbanner
+
+
+ På
+
+
+ Av
+
+
+ Redusering av infokapselbanner
+
+
+ Se færre bannere ved å automatisk avvise forespørsler om infokapsler, når det er mulig.
+
+ -->
+ Redusering av infokapselbanner
+
+
+ På for dette nettstedet
+
+
+ Nettstedet støttes for øyeblikket ikke
+
+
+ Av for dette nettstedet
+
+
+ Redusering av infokapselbanner
+
+
+ Av for dette nettstedet
+
+
+ På for dette nettstedet
+
+
+ Vil du slå på reduksjon av infokapselbannere for %1$s?
+
+
+ Vil du slå av reduksjon av infokapselbannere for %1$s?
+
+
+ %1$s vill slette infokapsler og oppdatere siden. Sletting av alle infokapsler kan føre til at du blir logget ut eller at handlekurver blir tømt.
+
+
+ %1$s kan prøve å automatisk avvise infokapselforespørsler.
+
+
+ Dette nettstedet er for tiden ikke støttet av Redusering av infokapselbanner. Vil du be om at teamet vårt vurderer denne nettsiden og legger til støtte i fremtiden?
+
+
+ Avbryt
+
+
+ Be om støtte
+
+
+ Forespørsel om støtte av nettsted er sendt inn.
+
+
+ Forespørsel om støtte av nettsted er sendt inn.
+
+
+
+ %1$s prøver å avvise infokapsellforespørsler for å avvise irriterende infokapselbannere.\n\nAdministrer innstillinger for infokapselbanner i %2$s.
+
+ innstillinger
+
+
+ Automatisk avspilling
+
+
+ For å tillate det:
+
+
+ 1. Gå til Android-innstillinger
+
+
+ Tillatelser]]>
+
+
+ Gå til Innstillinger
+
+
+ %1$s PÅ]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Plassering
+
+
+ Varsel
+
+
+ DRM-kontrollert innhold
+
+
+ Be om å tillate
+
+
+ Blokkert
+
+
+ Tillatt
+
+
+ Blokkert av Android
+
+
+ Tillat lyd og video
+
+
+ Blokker bare lyd
+
+
+ Anbefalt
+
+
+ Blokker lyd og video
+
+
+ Undersøkelser
+
+
+ Firefox kan installere og kjøre studier fra tid til annen.
+
+
+ Les mer
+
+
+ Applikasjonen avsluttes for å iverksette endringer
+
+
+ Fjern
+
+
+ Aktiv
+
+
+ Fullført
+
+
+ Ekstern debugging via USB/Wi-Fi
+
+
+ Lås opp
+
+
+ Bekreft ved bruk av fingeravtrykket ditt
+
+
+ Du kan bruke fingeravtrykket for å fortsette den nåværende appøkten.
+
+
+ Åpne lenke i ny økt
+
+
+ Fingeravtrykkikon
+
+
+ Fingeravtrykk ikke gjenkjent. Prøv igjen.
+
+
+ Fingeren rørte seg for fort. Prøv igjen.
+
+
+ Vise søkeforslag?
+
+
+ For å få forslag må %1$s sende det du skriver i adressefeltet til søkemotoren.
+
+
+ Nei
+
+
+ Ja
+
+
+ Noen søkemotorer kan ikke vise forslag.
+
+
+ Ignorer
+
+
+
+
+ Oppfører siden seg uventet?\n Prøv å deaktivere sporingsbeskyttelse
+
+
+ Legg til på startskjermen]]>
+
+
+ Åpne hver link i %1$s\n Angi %1$s som standardnettleser
+
+
+ Autofullfør nettadresser for nettsteder du bruker mest\n Trykk og hold på en nettadresse i adressefeltet
+
+
+ Åpne en lenke i en ny fane\n Trykk og hold på en lenke på en side
+
+
+ Slå av tips på startskjermen
+
+
+ Ny fane åpnet
+
+
+ Bytt
+
+
+ Starter fullskjermmodus
+
+
+ Bytt til lenke i ny fane med en gang
+
+
+ Blokker potensielt farlige og villedende nettsteder
+
+ Blokker rapporterte villedende- og angrepsnettsteder, skadeprogram-nettsteder og uønskede programvarenettsteder.
+
+
+ Kun-HTTPS-modus
+
+
+ Forsøker automatisk å koble til nettsteder ved hjelp av HTTPS-krypteringsprotokollen for økt sikkerhet.
+
+
+ Unntak
+
+ Du har slått av innholdsblokkering for disse nettstedene.
+
+ Fjern
+
+ Fjern alle nettsteder
+
+
+ Blokker infokapsler
+
+
+ Vil du blokkere infokapsler?
+
+
+ Fane krasjet
+
+ Beklager. Vi har et problem med denne fanen.
+
+ Som en privat nettleser lagrer vi aldri og kan ikke gjenopprette denne fanen.
+
+ Lukk fane
+
+
+
+
+
+ Send krasjrapport til Mozilla
+
+
+
+
+ Sporere blokkert siden %s
+
+ Innhold
+
+ Reklame
+
+ Sosialt
+
+ Analyse
+
+ Utvidet sporingsbeskyttelse
+
+ Beskyttelser er slått AV for dette nettstedet
+
+ Beskyttelse er slått PÅ for dette nettstedet
+
+ Tilkoblingen er sikker
+
+ Tilkoblingen er ikke sikker
+
+ Sporere og skript å blokkere
+
+
+ Gå tilbake
+
+
+
+ Fjern
+
+
+ Endre navn
+
+ Endre navn
+
+ Navn på snarvei
+
+
+ Lagrede og delte bilder <b>vil ikke bli</b> fjernet når du sletter %1$s-historikken
+
+
+
+ Tema
+
+ Lyst
+
+ Mørk
+
+ Innstilt av strømstyring
+
+ Følg enhetens tema
+
+
+ Dette nettstedet støtter ikke HTTPS
+
+
+ Les mer
+ Endre denne innstillingen i Innstillinger > Personvern og sikkerhet > Sikkerhet.]]>
+
+
+ Tilkoblingen er ikke sikker
+
+
+
+ Dersom du har klart å koble til serveren tidligere kan problemet være midlertidig, og du kan prøve igjen senere.
+ ]]>
+
+
+ Noen kan prøve å etterligne nettstedet, og det kan være risikabelt å fortsette.
+
+ %1$s stoler ikke på %2$s fordi sertifikatutstederen er ukjent, sertifikatet er selvsignert, eller serveren sender ikke de riktige mellomsertifikatene.
+ ]]>
+
+
+
+ Lukk fane
+
+
+
+ Fikk dem! Vi stoppet denne siden fra å spionere på deg. Trykk på skjoldet når som helst for informasjon om hva vi blokkerte.
+
+
+ Lukk sprettoppvindu
+
+
+
+ Du er beskyttet!
+
+ Disse standardinnstillingene gir sterk beskyttelse. Men det er enkelt å tilpasse innstillingene for å møte dine spesifikke behov.
+
+ Ignorer
+
+
+ Trykk her for å kaste alt — historikk, infokapsler, alt — og begynne på nytt på en ny fane.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Lukk
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Søke-widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Nettleserhistorikk er slettet! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start din private nettlesingsøkt, så blokkerer vi sporere og andre dårlige ting mens du surfer.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Vi overlater deg til din private nettlesing, men får en raskere start neste gang med %1$s-widgeten på startskjermen.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Legg til widget på startskjermen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget lagt til på startskjermen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ne-rNP/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000000..67b579d608
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ne-rNP/strings.xml
@@ -0,0 +1,574 @@
+
+
+
+
+
+
+
+
+ रद्द गर्नुहोस्
+
+ ठीक छ
+
+ बचत गर्नुहोस्
+
+
+ खोज्नुहोस् वा ठेगाना प्रबिष्ट गर्नुहोस्
+
+ स्वचालित निजी ब्राउजिङ्ग।\nब्राउज गर्नुहोस् । मेटाउनुहोस् । दोहोर्याउनुहोस् ।
+
+
+ तपाईंको ब्राउजिङ्ग इतिहास मेटाइयो।
+
+ ब्राउजिङ्ग इतिहास खाली गरियो
+
+
+ ट्याबको ब्राउजिङ्ग इतिहास मेटाइएको छ।
+
+
+ %1$s को लागि खोज्नुहोस्
+
+
+ सेयर…
+
+
+ साइट समस्या दर्ता गर्नुहोस्
+
+
+ %1$s मा खोल्नुहोस्
+
+
+ …मा खोल्नुहोस्
+
+
+ गृह स्क्रिनमा थप्नुहोस्
+
+
+ सर्टकटहरुमा थप्नुहोस्
+
+ सर्टकटहरुबाट हटाउनुहोस्
+
+ सेटिङ्गहरू
+ बारेमा
+ सहयोग
+ तपाईंका अधिकारहरु
+
+
+ ट्राकरहरु अवरुद्ध गरियो
+
+
+ यसलाई बन्द गर्नाले केही साइटका समस्याहरू समाधान गर्न सक्छ
+
+
+ सामग्री ब्लकिङ्ग
+
+ केहि साइटहरु ठीक गर्न यसलाई बन्द गर्नुहोस्
+
+
+ %1$s द्वारा सञ्चालित
+
+
+ मार्फत सेयर गर्नुहोस्
+
+
+ ब्राउजिङ्ग इतिहास खाली गर्नुहोस्
+
+
+ खोल्नुहोस्
+
+
+ मेटाउनुहोस् र खोल्नुहोस्
+
+
+ मेटाउनुहोस्
+
+
+ ब्राउजिङ्ग इतिहास खाली गर्नुहोस्
+
+
+
+ मेटाउनुहोस् र खोल्नुहोस्
+
+
+ मेटाउनुहोस् र %1$s खोल्नुहोस्
+
+
+
+ %1$s ले तपाईंलाई नियन्त्रणमा राख्छ।
+यसलाई निजी ब्राउजरको रूपमा प्रयोग गर्नुहोस्:
+
+ एपमानै खोज्नुहोस् र ब्राउज गर्नुहोस्
+ ट्र्याकरहरू रोक्नुहोस् (वा ट्र्याकरहरूलाई अनुमति दिन सेटिङ्गहरू अपडेट गर्नुहोस्)
+ कुकीजहरू मेटाउनका साथै खोज र ब्राउजिङ्ग इतिहास मेटाउनुहोस्
+
+
+%1$s Mozilla द्वारा उत्पादन गरिएको हो। हाम्रो लक्ष्य भनेको स्वस्थ, खुला इन्टरनेटको विकास गर्नु हो।
+थप जान्नुहोस्
]]>
+
+
+ गोपनीयता र सुरक्षा
+
+
+ ट्रयाकिङ, कुकीजहरू, डाटा विकल्पहरु
+
+
+ पूर्वनिर्धारित सेट गर्नुहोस्, स्वत: पूर्ण
+
+
+
+
+ %1$s को बारेमा, सहयोग
+
+
+ परिष्कृत ट्र्याकिङ्ग सुरक्षा
+
+
+ वेब सामग्री
+
+
+ एपहरु बदलिँदै
+
+
+ सामान्य
+
+
+ पूर्वनिर्धारित ब्राउजर, भाषा
+
+
+ डेटा सङ्कलन र प्रयोग
+
+ खोजी गर्नुहोस्
+
+
+ खोजी सुझावहरू प्राप्त गर्नुहोस्
+
+ %1$s ले तपाईले ठेगाना पट्टीमा टाइप गर्नुभएका कुराहरुलाई तपाईको खोज इन्जिनमा पठाउनेछ।
+
+
+ पूर्वनिर्धारित
+
+
+ खोजी इन्जिन
+
+
+ खुल्ला
+
+
+ बन्द
+
+
+ URL स्वत: पूर्ण
+
+
+ शीर्ष साइटहरूको लागि
+
+
+ ठेगाना पट्टीमा %s 450 भन्दा बढी लोकप्रिय URLs हरू स्वत: पूर्ण गर्न यसलाई सक्षम गर्नुहोस्।
+
+
+ तपाईले थप्नुभएका साइटहरुको लागि
+
+
+ आफ्नो मनपर्ने URLs हरू %s स्वतः पूरा गर्न सक्षम गर्नुहोस्।
+
+
+ साइटहरू प्रबन्ध गर्नुहोस्
+
+
+ साइटहरू प्रबन्ध गर्नुहोस्
+
+
+ + अनुकूल URL थप्नुहोस्
+
+
+ तपाईंको स्वत: पूर्ण सूची:
+
+
+ URL थप्नुहोस्
+
+
+ + अनुकूल URL थप्नुहोस्
+
+
+ अनुकूल URL थप्नुहोस्
+
+
+ स्वत: पुरा गर्न लिङ्क थप्नुहोस्
+
+
+ कुकीजहरु र साइट डेटा
+
+
+ डाटा विकल्पहरू
+
+
+ अनुकूल URLs हटाउनुहोस्
+
+
+ थप जान्नुहोस्
+
+
+ स्वत: पूर्ण URLहरू थप्नुहोस र व्यवस्थापन गर्नुहोस् ।
+
+
+ थप्नु पर्ने URL
+
+
+ URL टाँस्नुहोस् वा प्रविष्ट गर्नुहोस्
+
+
+ उदाहरण: mozilla.org
+
+
+ Example: example.com
+
+
+ नयाँ अनुकुल URL थपियो ।
+
+
+ हटाउनुहोस्
+
+
+ हटाउनुहोस्
+
+
+ तपाइँले प्रविष्ट गर्नुभएको URL पुन: जाँच गर्नुहोस् ।
+
+ भाषा
+
+ पूर्वनिर्धारित प्रणाली
+
+ गोपनीयता
+ विज्ञापन ट्रयाकरहरू अवरुद्द गर्नुहोस्
+ यदि तपाइँ विज्ञापनहरूमा क्लिक गर्नुहुन्न भने पनि, केहि विज्ञापनहरूले साइट भ्रमणहरू ट्र्याक गर्छन्
+ विश्लेषणात्मक ट्रयाकहरू अवरुद्द गर्नुहोस्
+ ट्याप गर्ने र स्क्रोल गर्ने जस्ता क्रियाकलापहरू संकलन, विश्लेषण र माप गर्न प्रयोग गरियो
+ सामाजिक ट्रयाकहरू अवरुद्द गर्नुहोस्
+ तपाईंको भ्रमणहरू ट्र्याक गर्न र साझेदारी बटनहरू जस्तै कार्यक्षमता प्रदर्शन गर्न साइटहरूमा स्थापना गरिएको
+ अन्य सामग्री ट्रयाकरहरू ब्लक गर्नुहोस्
+ सक्षम पार्नाले केहि पृष्ठहरू अनपेक्षित रूपमा व्यवहार गर्न सक्छन्
+ कुकीहरु ब्लक गर्नुहोस्
+
+
+ पर्दैन, धन्यबाद
+ तेस्रो-पक्ष ट्र्याकर कुकीजहरू मात्र रोक्नुहोस्
+ 3rd-party कुकीहरू मात्र ब्लक गर्नुहोस्
+
+ हुन्छ
+
+
+ एप अनलक गर्न फिङ्गरप्रिन्टको प्रयोग गर्नुहोस्
+
+
+ चुपचाप
+
+ अनुप्रयोगहरू स्विच गर्दा वेबपेजहरू लुकाउनुहोस्
+
+ सुरक्षा
+
+ कार्यसम्पादन
+ वेब फन्टहरू अवरुद्ध गर्नुहोस्
+
+ प्रतीकहरू वा तस्विरहरु हराउन सक्छन्
+
+ JavaScript ब्लक गर्नुहोस्
+
+
+ पृष्ठहरू छिटो लोड हुन सक्छ, तर अप्रत्याशित रूपमा व्यवहार पनि गर्न सक्छ
+
+
+ %1$s लाई पूर्वनिर्धारित ब्राउजर बनाउनुहोस्
+
+ Mozilla
+ उपयोग डाटा पठाउनुहोस्
+
+
+ अझ जान्नुहोस्
+
+
+ Mozilla सबैको लागि %1$s प्रदान र सुधार गर्न आवश्यक कुरा मात्र संकलन गर्ने प्रयास गर्दछ ।
+
+
+ गोपनीयता नीति
+
+
+ %1$s बारेमा
+
+
+ स्थापित खोज इन्जिनहरू
+
+
+ खोज इन्जिन छान्नुहोस्
+
+
+ पूर्वानिर्धारीत खोज इन्जिन पुनर्स्थापना गर्नुहोस्
+
+
+ + अर्को खोजी इन्जिन थप्नुहोस्
+ खोजी इन्जिनहरू हटाउनुहोस्
+ हटाउनुहोस्
+
+
+ अर्को खोज इन्जिन थप्नुहोस्
+
+ आफ्नो मनपर्ने इन्जिन छनौट गर्नुहोस्:
+
+
+ खोज इन्जिन थप्नुहोस्
+
+ खोजी इन्जिनको नाम
+ प्रयोग गर्न स्ट्रिङ खोजी गर्नुहोस्
+ सङ्ग्रह गर्नुहोस्
+
+
+ Example: example.com/search/?q=%s
+
+ नयाँ खोजी इन्जिन थपियो ।
+
+ खोजी इन्जिनको नाम प्रविष्ट गर्नुहोस्
+ एक स्थापित खोजि इन्जिनले पहिले नै त्यो नाम प्रयोग गरिरहेको छ ।
+
+ खोजी स्ट्रिङ प्रविष्ट गर्नुहोस्
+
+ जाँच गर्नुहोस् कि, खोज अक्षर उदाहरणको ढाँचा सङ्ग मेल खान्छ
+
+
+ आगत खाली गर्नुहोस्
+
+
+ खारेज गर्नुहोस्
+
+
+ ब्राउजिङ्ग इतिहास खाली गर्नुहोस्
+
+
+ खुला ट्याबहरु: %1$s
+
+
+ सुरक्षित जडान
+
+
+ लोड हुँदैछ
+
+
+ वेबसाइट लोड भयो
+
+
+ थप विकल्पहरू
+
+
+ थप बिकल्पहरु बटन
+
+
+ अगाडि जानुहोस्
+
+
+ वेबसाइट पुन: लोड गर्नुहोस्
+
+
+ पछाडि जानुहोस्
+
+
+ वेबसाइट लोड गर्न रोक्नुहोस्
+
+
+ पहिलेको एपमा फर्कनुहोस्
+
+
+ अवरुद्ध गरिएका ट्रयाकरहरुको सङ्ख्या
+
+
+ ट्रयाकरहरू अवरुद्द गर्नुहोस्
+
+ तपाईँका अधिकारहरु
+
+ लिङ्कलाई अर्को अनुप्रयोगमा खोल्नुहोस्
+
+ तपाईं %2$s मा यो लिङ्क खोल्नका लागि %1$s छोड्न सक्नुहुन्छ।
+
+ लिङ्क खोल्न सक्ने अनुप्रयोग खोज्नुहोस्
+
+ तपाईँको उपकरणमा कुनै पनि एपले यो लिङ्क खोल्न सक्षम छैन। %2$s खोल्न सक्ने एउटा एपको खोज्नका लागि, तपाईंले %1$s छोड्न सक्नुहुन्छ।
+
+ निजी ब्राउजिङ्ग बन्द गर्न चाहनुहुन्छ ?
+
+
+ %1$s सकियो
+
+
+ खोल्नुहोस्
+
+
+
+
+
+
+
+ सर्भर फेला परेन
+
+
+
+
+
+
+
+
+
+
+
+
+ आफ्नो गोपनीयताको बलियो बनाउनुहोस्
+
+
+ निजी ब्राउजिङ्गलाई अर्को स्तरमा पुर्याउनुहोस्। विज्ञापन र अन्य सामग्रीहरू ब्लक गर्नुहोस् जसले, तपाईंलाई साइटहरूमा ट्र्याक गर्न सक्छ र पृष्ठ लोड गर्दा लाग्ने समयलाई घटाउने गर्न सक्छ।
+
+
+ तपाईंको खोजी, तपाईंको बाटो
+
+
+ केहि फरक खोज्दै हुनुहुन्छ ? सेटिङ्गहरूमा अर्को पूर्वनिर्धारित खोजी इञ्जिन छनौट गर्नुहोस्।
+
+
+ तपाईंको गृह स्क्रिनमा सर्टकट थप्नुहोस्
+
+
+ छिटै %1$s मा आफ्नो मनपर्ने साइटहरूमा फर्किनुहोस्। केबल %1$s मेनुबाट \"गृह स्क्रिनमा थप्नुहोस्\" चयन गर्नुहोस्।
+
+
+ गोपनीयताको बानी बसाल्नुहोस्
+
+
+ %1$s लाई आफ्नो पूर्वनिर्धारित ब्राउजरको रूपमा सेट गर्नुहोस् र तपाईंले अन्य एपहरूबाट वेबपृष्ठहरू खोल्दा निजी ब्राउजिङ्गका फाइदाहरू प्राप्त गर्नुहोस्।
+
+ ठीक छ, थाहा पाएँ
+ छोड्नुहोस्
+ अर्को
+
+
+ -
+
+
+ थप्नुहोस्
+
+
+ हो
+
+
+ रद्द गर्नुहोस्
+
+
+ होइन
+
+
+ सर्टकट परिष्कृत ट्र्याकिङ्ग सुरक्षा असक्षम पारेर खुल्नेछ
+
+
+ निजी ब्राउजिङ्ग सत्र
+
+
+ सूचनाहरूले तपाइँलाई तपाइँको %1$s सत्र एक ट्यापको साथ मेटाउन दिन्छ। तपाईले एप खोल्नु नै पर्दैन वा तपाईको ब्राउजरमा के चलिरहेको छ हेर्नु पर्दैन।
+
+
+ ब्राउजिङ्ग इतिहास खाली गर्नुहोस्
+
+
+ Firefox डाउनलोड गर्नुहोस्
+
+
+ प्रयोगकर्ता नाम
+ गोप्यशब्द
+ खाली गर्नुहोस्
+
+
+
+ सुरक्षित जडान
+ असुरक्षित जडान
+
+ रुजू गरेको: %1$s
+
+
+ साईट सुरक्षा
+ URL पहिल्यै अवस्थित छ
+
+
+ पृष्ठमा फेला पार्नुहोस्
+
+
+
+
+ डेस्कटप साईट अनुरोध गर्नुहोस्
+
+
+ URL प्रतिलिपि गरियो
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-night/colors.xml b/mobile/android/focus-android/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000000..06da3b1f0b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,146 @@
+
+
+
+ @color/photonInk60
+ @color/photonInk05
+ @color/photonLightGrey05
+ @color/photonLightGrey50
+
+ @color/photonWhite
+
+ @color/photonRed30
+
+ #e452B9
+
+ #66FBFBFE
+ @color/colorPrimary
+ @color/photonInk60
+
+ #80FFFFFF
+ @color/photonLightGrey50
+ @color/background
+
+ #ccffffff
+ #cc000000
+
+ @color/photonInk05
+
+ @color/photonInk10
+ @color/photonPink40
+ @color/photonPink70
+ #FF6A75
+
+
+ #FF1E1236
+ #FF21163E
+ #FF1D1133
+
+ #FF1E1236
+ #FF332866
+ #FF4D3A83
+ #FF3C286A
+ #FF21163E
+ #FF1D1133
+
+
+ #FFFF1AD9
+ #FFFF1AD9
+ #FFFF1AD9
+
+ @color/photonMagenta70
+ @color/photonMagenta80
+
+ @color/photonBlue40
+ @color/photonBlue40
+
+
+ @color/colorPrimary
+
+ #353852
+ @color/photonLightGrey05
+ @color/colorSecondary
+
+
+ @color/colorSecondary
+ @color/primaryText
+ @color/accentBright
+
+
+ @color/photonInk80
+ @color/photonInk80
+ @color/photonLightGrey05
+ @color/photonLightGrey05
+ @color/colorSecondary
+ @color/accentBright
+
+ #ffffffff
+ @color/photonInk80
+ #480f1126
+ #ffb5007f
+
+
+ #7FFFFFFF
+ #FFFFFFFF
+ #7542E5
+ #E0E0E6
+ #3B3C3F
+
+
+ @color/photonInk05
+ @color/photonInk60
+
+
+ @color/photonInk60
+
+ @color/focusContrastPink
+
+ #b2b2b2b2
+ #FF2AA1FE
+
+
+ @color/secondaryText
+ @color/photonInk05
+ @color/accentBright
+ @color/accentBright
+
+ @color/photonGrey10
+ @color/accentBright
+
+ #353852
+ #B3313131
+
+ @color/background
+
+ @color/photonWhite
+
+
+ @color/photonGrey50
+ @color/photonGrey90
+
+
+ @color/colorSecondary
+
+
+ @color/primaryText
+ @color/colorPrimary
+ @color/secondaryText
+
+
+ #7542E5
+ #1E1236
+ #332866
+ #4D3A83
+ #3C286A
+ #21163E
+ #1D1133
+
+
+ #2B2A33
+ #3B3C3F
+ #7542E5
+
+
+ @color/photonViolet20
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-nl/strings.xml b/mobile/android/focus-android/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000000..4c6043057a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ Annuleren
+
+ OK
+
+ Opslaan
+
+
+ Voer zoekterm of adres in
+
+ Automatische privénavigatie.\nSurfen. Wissen. Herhalen.
+
+
+ Uw navigatiegeschiedenis is gewist.
+
+ Navigatiegeschiedenis gewist
+
+
+ Navigatiegeschiedenis van tabblad is gewist.
+
+
+ Zoeken naar %1$s
+
+
+ Delen…
+
+
+ Websiteprobleem rapporteren
+
+
+ Openen in %1$s
+
+
+ Openen in…
+
+
+ Toevoegen aan startscherm
+
+
+ Aan snelkoppelingen toevoegen
+
+ Verwijderen uit Snelkoppelingen
+
+
+ Instellingen
+ Over
+ Help
+ Uw rechten
+
+
+ Trackers geblokkeerd
+
+
+ Uitschakelen hiervan kan bepaalde websiteproblemen verhelpen
+
+
+ Inhoudsblokkering
+
+ Schakel dit uit om bepaalde websites te repareren
+
+
+ Mogelijk gemaakt door %1$s
+
+
+ Delen via
+
+ Navigatiegeschiedenis wissen?
+ Tik op deze notificatie of sluit hem om uw navigatiegeschiedenis veilig te wissen.
+
+
+ Tik op deze notificatie of veeg er overheen om uw navigatiegeschiedenis veilig te wissen.
+
+ Navigatiegeschiedenis wissen
+
+
+ Openen
+
+
+ Wissen en openen
+
+
+ Wissen
+
+
+ Navigatiegeschiedenis wissen
+
+
+
+ Wissen en openen
+
+
+ Wissen en %1$s openen
+
+
+
+ Zoeken in Focus
+
+ Zoeken in Klar
+
+ Zoeken in Focus Beta
+
+ Zoeken in Focus Nightly
+
+
+ %1$s geeft u de controle.
+Gebruik als een privébrowser:
+
+ Rechtstreeks vanuit de app zoeken en navigeren
+ Trackers blokkeren (of instellingen bijwerken om ze toe te staan)
+ Wissen verwijdert zowel cookies als zoek- en navigatiegeschiedenis
+
+
+%1$s wordt gemaakt door Mozilla. Onze missie is het verzorgen van een gezond en open internet.
+Meer info
]]>
+
+
+ Privacy en beveiliging
+
+
+ Volgen, cookies, gegevenskeuzes
+
+
+ Standaard instellen, automatisch aanvullen
+
+
+
+
+ Over %1$s, help
+
+
+ Verbeterde bescherming tegen volgen
+
+
+ Webinhoud
+
+
+ Wisselen tussen apps
+
+
+ Algemeen
+
+
+ Standaardbrowser, taal
+
+
+ Gegevensverzameling en -gebruik
+
+ Zoeken
+
+
+ Zoeksuggesties verkrijgen
+
+ %1$s zal wat u in de adresbalk intypt naar uw zoekmachine sturen
+
+
+ Standaard
+
+
+ Zoekmachine
+
+
+ Aan
+
+
+ Uit
+
+
+ URL’s automatisch aanvullen
+
+
+ Voor topwebsites
+
+
+ Schakel dit in om %s automatisch meer dan 450 populaire URL’s in de adresbalk te laten aanvullen.
+
+
+ Voor websites die u toevoegt
+
+
+ Schakel dit in om %s automatisch uw favoriete URL’s te laten aanvullen.
+
+
+ Websites beheren
+
+
+ Websites beheren
+
+
+ + Aangepaste URL toevoegen
+
+
+ Uw lijst voor automatisch aanvullen:
+
+
+ URL toevoegen
+
+
+ Aangepaste URL toevoegen
+
+
+ Aangepaste URL toevoegen
+
+
+ Koppeling naar automatisch aanvullen toevoegen
+
+
+ Cookies en websitegegevens
+
+
+ Gegevenskeuzes
+
+
+ Aangepaste URL’s verwijderen
+
+
+ Meer info
+
+
+ Eigen URL’s voor automatisch aanvullen toevoegen en beheren
+
+
+ Toe te voegen URL
+
+
+ Plak of voer URL in
+
+
+ Voorbeeld: mozilla.org
+
+
+ Voorbeeld: example.com
+
+
+ Nieuwe aangepaste URL toegevoegd.
+
+
+ Verwijderen
+
+
+ Verwijderen
+
+
+ Controleer de ingevoerde URL.
+
+ Taal
+
+ Systeemstandaard
+
+ Privacy
+ Advertentietrackers blokkeren
+ Sommige advertenties volgen websitebezoeken, zelfs als u niet op de advertenties klikt
+ Analysetrackers blokkeren
+ Gebruikt voor het verzamelen, analyseren en meten van activiteiten zoals tikken en scrollen
+ Sociale trackers blokkeren
+ Ingebed op websites om uw bezoeken te volgen en functionaliteiten zoals deelknoppen weer te geven
+ Andere inhoudstrackers blokkeren
+ Inschakelen kan onverwacht gedrag van bepaalde pagina’s veroorzaken
+ Cookies blokkeren
+
+
+ Nee, bedankt
+ Alleen tracking-cookies van derden blokkeren
+ Alleen cookies van derden blokkeren
+
+ Cross-sitecookies blokkeren
+ Ja, graag
+
+
+ Vingerafdruk voor ontgrendelen van app gebruiken
+
+
+ Ontgrendel met vingerafdruk als u snelkoppelingen hebt toegevoegd of als een website al in %s is geopend.
+
+
+ Stealth
+
+ Webpagina’s verbergen bij wisselen van apps en maken van schermafbeeldingen blokkeren
+
+ Beveiliging
+
+ Prestaties
+ Weblettertypen blokkeren
+
+ Kan tot ontbrekende pictogrammen of afbeeldingen leiden
+
+ JavaScript blokkeren
+
+ Pagina’s kunnen sneller worden geladen, maar kunnen zich ook onverwacht gedragen
+
+
+ %1$s standaardbrowser maken
+
+ Mozilla
+ Gebruiksgegevens verzenden
+
+
+ Meer info
+
+
+ Mozilla streeft ernaar alleen te verzamelen wat nodig is om %1$s voor iedereen aan te bieden en te verbeteren.
+
+
+ Privacyverklaring
+
+
+ Licentie-informatie
+
+
+ Gebruikte bibliotheken
+
+
+ %s | OSS-bibliotheken
+
+
+ Over %1$s
+
+
+ Geïnstalleerde zoekmachines
+
+
+ Zoekmachine kiezen
+
+
+ Standaardzoekmachines terugzetten
+
+
+ + Nog een zoekmachine toevoegen
+ Zoekmachines verwijderen
+ Verwijderen
+
+
+ Nog een zoekmachine toevoegen
+
+ Selecteer uw gewenste zoekmachine:
+
+
+ Zoekmachine toevoegen
+
+ Naam van zoekmachine
+ Te gebruiken zoekstring
+ Opslaan
+
+
+ Voorbeeld: example.com/search/?q=%s
+
+ Nieuwe zoekmachine toegevoegd.
+
+ Voer naam van zoekmachine in
+ Deze naam wordt al door een geïnstalleerde zoekmachine gebruikt.
+
+ Voer zoekstring in
+
+ Controleer of zoekstring aan voorbeeldnotatie voldoet
+
+
+ Invoer wissen
+
+
+ Verwijderen
+
+
+ Navigatiegeschiedenis wissen
+
+
+ Open tabbladen: %1$s
+
+
+ Beveiligde verbinding
+
+
+ Laden
+
+
+ Website geladen
+
+
+ Meer opties
+
+
+ Knop voor meer opties
+
+
+ Vooruit bladeren
+
+
+ Website opnieuw laden
+
+
+ Terug bladeren
+
+
+ Laden van website stoppen
+
+
+ Terug naar vorige app
+
+
+ Aantal geblokkeerde trackers
+
+
+ Trackers blokkeren
+
+ Uw rechten
+
+ Koppeling openen in andere app
+
+ U kunt %1$s verlaten om deze koppeling in %2$s te openen.
+
+ Een app zoeken die de koppeling kan openen
+
+ Geen enkele app op uw apparaat kan deze koppeling openen. U kunt %1$s verlaten om bij %2$s naar een app te zoeken die dat wel kan.
+
+ Privénavigatie afsluiten?
+
+
+ %1$s is voltooid
+
+
+ Openen
+
+
+
+
+
+
+
+
+
+
+ Toegevoegd aan snelkoppelingen!
+
+ Server niet gevonden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sluiten
+
+
+
+ Welkom bij %1$s
+
+
+ Snel. Privé. Geen afleiding.
+
+
+ Beginnen
+
+
+
+ %1$s is niet zoals andere browsers
+
+
+ Wanneer u de app sluit wissen we uw geschiedenis voor extra privacy.
+
+
+
+ Maak %1$s uw standaard om uw gegevens te beschermen bij elke koppeling die u opent.
+
+
+ Als standaardbrowser instellen
+
+
+ Overslaan
+
+
+
+ Vergroot uw privacy
+
+ Breng privénavigatie naar een hoger niveau. Blokkeer advertenties en andere inhoud die u op websites kunnen volgen en laadtijden van pagina’s verlengen.
+
+
+ Zoeken op uw manier
+
+ Op zoek naar iets anders? Kies een andere standaardzoekmachine in Instellingen.
+
+
+ Voeg koppelingen toe aan uw startscherm
+
+ Keer snel terug naar uw favoriete websites in %1$s. Selecteer gewoon ‘Toevoegen aan startscherm’ vanuit het %1$s-menu.
+
+
+ Maak privacy een gewoonte
+
+ Stel %1$s in als uw standaardbrowser en ervaar de voordelen van privénavigatie als u webpagina’s opent vanuit andere apps.
+
+ OK, begrepen!
+ Overslaan
+ Volgende
+
+
+ -
+
+
+ Toevoegen
+
+
+ JA
+
+
+ Annuleren
+
+
+ NEE
+
+
+ Snelkoppeling opent met Verbeterde bescherming tegen volgen uitgeschakeld
+
+
+ Privénavigatiesessie
+
+
+ Met notificaties kunt u via een tik uw %1$s-sessie wissen. U hoeft de app niet te openen of te zien wat er in uw browser actief is.
+
+
+ Navigatiegeschiedenis wissen
+
+
+ Firefox downloaden
+
+
+
+
+
+
+
+
+ Mozilla Public License en andere opensourcelicenties.]]>
+
+
+ hier te vinden.]]>
+
+
+ licenties.]]>
+
+
+ GNU General Public License v3 en die hier beschikbaar zijn.]]>
+
+
+ Gebruikersnaam
+ Wachtwoord
+ Wissen
+
+
+
+ Beveiligde verbinding
+ Niet-beveiligde verbinding
+
+ Geverifieerd door: %1$s
+
+
+ Websitebeveiliging
+ URL bestaat al
+
+
+ Zoeken op pagina
+
+
+ Zoeken op pagina
+
+
+ %1$d/%2$d
+
+ %1$d van %2$d
+
+
+ Volgende resultaat vinden
+
+ Vorige resultaat vinden
+
+ Zoeken op pagina sluiten
+
+
+
+
+ Desktopwebsite opvragen
+
+
+ Desktopwebsite
+
+
+ URL gekopieerd
+
+
+ Ontwikkelaarshulpmiddelen
+
+
+ Koppelingen in apps openen
+
+
+ Geavanceerd
+
+
+ Websitemachtigingen
+
+
+ Reductie van cookiebanners
+
+
+ Aan
+
+
+ Uit
+
+
+ Reductie van cookiebanners
+
+
+ Zie minder banners door, indien mogelijk, cookieverzoeken automatisch te weigeren.
+
+ -->
+ Reductie van cookiebanners
+
+
+ AAN voor deze website
+
+
+ Website wordt momenteel niet ondersteund
+
+
+ UIT voor deze website
+
+
+ Reductie van cookiebanners
+
+
+ UIT voor deze website
+
+
+ AAN voor deze website
+
+
+ Reductie van cookiebanners inschakelen voor %1$s?
+
+
+ Reductie van cookiebanners uitschakelen voor %1$s?
+
+
+ %1$s wist de cookies voor deze website en vernieuwt de pagina. Als alle cookies worden gewist, wordt u mogelijk afgemeld of worden winkelwagentjes geleegd.
+
+
+ %1$s kan proberen cookieverzoeken automatisch te weigeren.
+
+
+ Deze website wordt momenteel niet ondersteund door Reductie van cookiebanners. Wilt u ons team verzoeken deze website te beoordelen en in de toekomst ondersteuning toe te voegen?
+
+
+ Annuleren
+
+
+ Ondersteuning vragen
+
+
+ Verzoek om websiteondersteuning ingediend.
+
+
+ Verzoek om websiteondersteuning ingediend.
+
+
+
+ %1$s probeert cookieverzoeken af te wijzen om irritante cookiebanners te verwijderen.\n\nBeheer voorkeuren voor cookiebanners in %2$s.
+
+
+ instellingen
+
+
+ Automatisch afspelen
+
+
+ Toestaan:
+
+
+ 1. Ga naar Android-instellingen
+
+
+ Machtigingen]]>
+
+
+ Naar Instellingen
+
+
+ %1$s op AAN]]>
+
+
+ Camera
+
+
+ Microfoon
+
+
+ Locatie
+
+
+ Notificatie
+
+
+ DRM-beheerde inhoud
+
+
+ Vragen om toestemming
+
+
+ Geblokkeerd
+
+
+ Toegestaan
+
+
+ Geblokkeerd door Android
+
+
+ Audio en video toestaan
+
+
+ Alleen audio blokkeren
+
+
+ Aanbevolen
+
+
+ Audio en video blokkeren
+
+
+ Onderzoeken
+
+
+ Firefox kan af en toe onderzoeken installeren en uitvoeren.
+
+
+ Meer info
+
+
+ De toepassing wordt afgesloten om wijzigingen toe te passen
+
+
+ Verwijderen
+
+
+ Actief
+
+
+ Voltooid
+
+
+ Remote debugging via USB/wifi
+
+
+ Ontgrendelen
+
+
+ Bevestig het gebruik van uw vingerafdruk
+
+
+ U kunt uw vingerafdruk gebruiken om uw huidige app-sessie voort te zetten.
+
+
+ Koppeling openen in nieuwe sessie
+
+
+ Vingerafdrukpictogram
+
+
+ Vingerafdruk niet herkend. Probeer het opnieuw.
+
+
+ Vinger te snel bewogen. Probeer het opnieuw.
+
+
+ Zoeksuggesties tonen?
+
+
+ Om suggesties te verkrijgen, moet %1$s wat u in de adresbalk intypt naar de zoekmachine sturen.
+
+
+ Nee
+
+
+ Ja
+
+
+ Sommige zoekmachines kunnen geen suggesties tonen.
+
+
+ Verwijderen
+
+
+
+
+ Gedraagt een website zich onverwacht?\n Probeer Bescherming tegen volgen uit te schakelen
+
+
+ Toevoegen aan startscherm]]>
+
+
+ Alle koppelingen in %1$s openen\n Stel %1$s in als uw standaardbrowser
+
+
+ URL’s voor meestgebruikte websites automatisch aanvullen\n Houd uw vinger op een URL in de adresbalk
+
+
+ Een koppeling in een nieuw tabblad openen\n Houd uw vinger op een koppeling op een pagina
+
+
+ Tips op het startscherm uitschakelen
+
+
+ Nieuw tabblad geopend
+
+
+ Wisselen
+
+
+ Volledig scherm wordt geopend
+
+
+ Onmiddellijk naar koppeling in nieuw tabblad wisselen
+
+
+ Mogelijk gevaarlijke en misleidende websites blokkeren
+
+ Gerapporteerde misleidende en aanvalsites, malwaresites en ongewenste-software-websites blokkeren.
+
+
+ Alleen-HTTPS-modus
+
+
+ Probeert voor een betere beveiliging automatisch middels het HTTPS-versleutelingsprotocol verbinding te maken met websites.
+
+
+ Uitzonderingen
+
+ U hebt inhoudsblokkering voor deze websites uitgeschakeld.
+
+ Verwijderen
+
+ Alle websites verwijderen
+
+
+ Cookies blokkeren
+
+
+ Wilt u cookies blokkeren?
+
+
+ Tabblad gecrasht
+
+ Sorry. Er is een probleem met dit tabblad.
+
+ Als privébrowser slaan we nooit iets op en kunnen we dit tabblad niet herstellen.
+
+ Tabblad sluiten
+
+
+
+
+
+ Crashrapport naar Mozilla verzenden
+
+
+
+
+ Trackers geblokkeerd sinds %s
+
+ Inhoud
+
+ Advertenties
+
+ Sociaal
+
+ Analyse
+
+ Verbeterde bescherming tegen volgen
+
+ Beschermingen voor deze website staan UIT
+
+ Beschermingen voor deze website staan AAN
+
+ Verbinding is beveiligd
+
+ Verbinding is niet beveiligd
+
+ Te blokkeren trackers en scripts
+
+
+ Terug
+
+
+
+ Verwijderen
+
+
+ Hernoemen
+
+ Hernoemen
+
+
+ Naam snelkoppeling
+
+
+ Opgeslagen en gedeelde afbeeldingen <b>worden niet</b> verwijderd als u geschiedenis van %1$s wist
+
+
+
+ Thema
+
+ Licht
+
+ Donker
+
+ Ingesteld door Batterijbesparing
+
+ Apparaatthema volgen
+
+
+
+ Deze website ondersteunt geen HTTPS
+
+
+ Meer info
+Wijzig deze instelling in Instellingen > Privacy en beveiliging > Beveiliging.]]>
+
+
+ Verbinding niet beveiligd
+
+
+
+ Als u in het verleden met succes verbinding hebt gemaakt met deze server, is de fout mogelijk tijdelijk.
+ ]]>
+
+
+ Iemand kan proberen de website na te doen en verdergaan kan risicovol zijn.
+
+ %1$s vertrouwt %2$s niet, omdat de certificaatuitgever onbekend is, het certificaat zelfondertekend is, of de server niet de juiste intermediaire certificaten verzendt.
+ ]]>
+
+
+
+ Tabblad sluiten
+
+
+
+ Hebbes! We hebben ervoor gezorgd dat deze website u niet meer bespioneert. Tik op elk moment op het schild om te bekijken wat we blokkeren.
+
+
+ Pop-up sluiten
+
+
+
+ U bent beschermd!
+
+ Deze standaardinstellingen bieden een sterke bescherming. Maar het is eenvoudig om de instellingen aan uw specifieke behoeften aan te passen.
+
+ Sluiten
+
+
+ Tik hier om alles weg te gooien – geschiedenis, cookies, alles – en een verse start te maken op een nieuw tabblad.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Sluiten
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Zoekwidget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Navigatiegeschiedenis gewist! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start uw privénavigatiesessie en we blokkeren trackers en andere slechte dingen terwijl u bezig bent.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ We laten u nu verdergaan met uw privénavigatie, u kunt de volgende keer sneller van start gaan nu met de %1$s-widget op uw startscherm.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Widget aan startscherm toevoegen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget aan startscherm toegevoegd
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-nn-rNO/strings.xml b/mobile/android/focus-android/app/src/main/res/values-nn-rNO/strings.xml
new file mode 100644
index 0000000000..161e4d4919
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-nn-rNO/strings.xml
@@ -0,0 +1,1127 @@
+
+
+
+
+
+
+
+
+ Avbryt
+
+ OK
+
+ Lagra
+
+
+ Søk eller skriv inn ei adresse
+
+ Automatisk privat nettlesing.\nSurf. Slett. Repeter.
+
+
+ Nettlesarhistorikken din er sletta.
+
+ Nettlesarhistorikk er sletta
+
+
+ Nettlesarhistorikken til fana er sletta.
+
+
+ Søk etter %1$s
+
+
+ Del…
+
+
+ Rapporter problem med nettstad
+
+
+ Opna i %1$s
+
+
+ Opna i…
+
+
+ Legg til på startskjerm
+
+
+ Legg til i snarvegar
+
+ Fjern frå snarvegar
+
+
+ Innstillingar
+ Om
+ Hjelp
+ Dine rettar
+
+
+ Sporfølgjarar blokkerte
+
+
+ Om du slår av dette, kan det løyse nokre problem med nettsida
+
+
+ Innhaldsblokkering
+
+ Slå av for å fikse nokre nettsider
+
+
+ Driven av %1$s
+
+
+ Del via
+
+ Slette nettlesarhistorikk?
+
+ Trykk eller fjern dette varselet for å slette nettlesarhistorikken din på ein trygg måte.
+
+
+ Trykk eller sveip dette varselet for å slette nettlesarhistorikken din på ein trygg måte.
+
+ Slett nettlesarhistorikk
+
+
+ Opna
+
+
+ Slett og opna
+
+
+ Slett
+
+
+ Slett nettlesarhistorikken
+
+
+
+ Slett og opne
+
+
+ Slett og opne %1$s
+
+
+
+ Søk med Focus
+
+
+ Søk med Klar
+
+ Søk med Focus Beta
+
+ Søk med Focus Nightly
+
+
+ %1$s gir deg kontroll.
+Bruk han som ein privat nettlesar:
+
+ Søk og surf rett i appen
+ Blokker sporarar (eller oppdater innstillingane for å tillate sporarar)
+ Slett for å fjerne infokapslar samt søk- og nettlesarhistorikk
+
+
+%1$s er produsert av Mozilla. Vårt oppdrag er å fremme eit sunt, ope Internett.
+Les meir
]]>
+
+
+ Personvern og sikkerheit
+
+
+ Sporing, infokapslar, dataval
+
+
+ Set standard, autofullfør
+
+
+
+
+ Om %1$s, hjelp
+
+
+ Utvida sporingsvern
+
+
+ Nettinnhald
+
+
+ Byte mellom appar
+
+
+ Generelt
+
+
+ Standardnettlesar, språk
+
+
+ Datainnsamling og bruk
+
+ Søk
+
+
+ Få søkjeforslag
+
+ %1$s sender det du skriv i adressefeltet til søkjemotoren din
+
+
+ Standard
+
+
+ Søkjemotor
+
+
+ På
+
+
+ Av
+
+
+ Autofullfør nettadresse
+
+
+ For toppnettsider
+
+
+ Slå på at %s autofullfører over 450 populære nettadresser i adressefeltet.
+
+
+ For nettsider du legg til
+
+
+ Slå på for å få %s til å autofullføre dei føretrekte nettadressene dine.
+
+
+ Handsam sider
+
+
+ Handsam sider
+
+
+ + Legg til ei tilpassa nettadresse
+
+
+ Di liste over autofullføringar:
+
+
+ Legg til URL
+
+
+ Legg til ei tilpassa nettadresse
+
+
+ Legg til ei tilpassa nettadresse
+
+
+ Legg til lenke til autofullfør
+
+
+ Infokapslar og nettsidedata
+
+
+ Dataval
+
+
+ Fjern tilpassa nettadresser
+
+
+ Les meir
+
+
+ Legg til og handsam tilpassa autofullførte nettadressesr.
+
+
+ Nettadresse å legga til
+
+
+ Lim inn eller skriv inn ei nettadresse
+
+
+ Døme: mozilla.org
+
+
+ Døme: example.com
+
+
+ Ny tilpassa nettadresse lagt til.
+
+
+ Fjern
+
+
+ Fjern
+
+
+ Dobbelsjekk nettadressa du skreiv inn.
+
+ Språk
+
+ Systemstandard
+
+ Personvern
+ Blokker reklamesporfølgjarar
+ Nokre annonsar sporfølgjarar nettstadbesøk, sjølv om du ikkje klikkar på annonsane
+ Blokker analysesporfølgjarar
+ Brukt for å samla inn, analysera og måla aktivitetar som tastetrykk og rulling
+ Blokker sosiale media-sporfølgjarar
+ Innebygd på nettstader for å spora besøka dine og å visa funksjonalitet som dele-knappar
+ Blokker andre innhaldssporfølgjarar
+ Aktivering kan føre til at nokre sider oppfører seg uventa
+ Blokker infokapslar
+
+
+ Nei takk
+ Blokker berre tredjepartssporings-infokapslar
+ Blokker berre tredjepartsinfokapslar
+
+ Blokker infokapslar på tvers av nettstadar
+ Ja takk
+
+
+ Bruk fingeravtrykk for å låse opp appen
+
+
+ Lås opp med fingeravtrykk dersom du har lagt til snarvegar eller når ein nettstad allereie er open i %s.
+
+
+ Usynleg løyndmodus
+
+ Gøym nettsider når du byter appar og unngå at det vert knipsa skjermbilde.
+
+ Sikkerheit
+
+ Yting
+ Blokker nettfontar
+
+ Kan resultere i at ikon eller bilde manglar
+
+ Blokker JavaScript
+
+ Sider kan lastast raskare, men kan òg oppføra seg uventa
+
+
+ Bruk %1$s som standard nettlesar
+
+ Mozilla
+ Send brukardata
+
+
+ Les meir
+
+
+ Mozilla strevar etter å berre samla inn det vi treng for å tilby og forbetra %1$s for alle.
+
+
+ Personvernerklæring
+
+
+ Lisensinformasjon
+
+
+ Bibliotek som vi brukar
+
+
+ %s | OSS-bibliotek
+
+
+ Om %1$s
+
+
+ Installerte søkjemotorar
+
+
+ Vel søkjemotor
+
+
+ Tilbakestill til standardsøkjemotorar
+
+
+ + Legg til ein annan søkjemotor
+ Fjern søkjemotorar
+ Fjern
+
+
+ Legg til ein annan søkjemotor
+
+
+ Vel føretrekt søkjemotor:
+
+
+ Legg til søkjemotor
+
+ Søkjemotornamn
+ Søkjestreng som skal brukast
+ Lagra
+
+
+ Døme: example.com/search/?q=%s
+
+ Ny søkjemotor lagt til.
+
+ Skriv inn namn på søkjemotor
+ Ein installert søkjemotor brukar allereie dette namnet.
+
+ Skriv inn søkjestreng
+
+ Kontroller at søkjestrengen passar med dømeformatet
+
+
+ Tøm skrivefeltet
+
+
+ Avvis
+
+
+ Slett nettlesarhistorikken
+
+
+ Opne faner: %1$s
+
+
+ Trygg tilkopling
+
+
+ Lastar
+
+
+ Nettside lasta
+
+
+ Fleire innstillingar
+
+
+ Knappen fleire alternativ
+
+
+ Naviger framover
+
+
+ Last sida på nytt
+
+
+ Naviger tilbake
+
+
+ Stopp lasting av nettsida
+
+
+ Gå tilbake til førre app
+
+
+ Tal på blokkerte sporfølgjarar
+
+
+ Blokker sporfølgjarar
+
+ Dine rettar
+
+ Opna lenke i ein annan app
+
+ Du kan gå ut av %1$s for å opna denne lenka i %2$s.
+
+ Finn ein app som kan opna lenka
+
+ Ingen av appane på eininga di kan opna denne lenka. Du kan gå ut av %1$s for å søka på %2$s etter ein app som kan gjera dette.
+
+ Avslutta privat nettlesing?
+
+
+ %1$s ferdig
+
+
+ Opna
+
+
+
+
+
+
+
+
+
+
+ Lagt til i snarvegar!
+
+ Fann ikkje serveren
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lat att
+
+
+
+ Velkomen til %1$s
+
+
+ Rask. Privat. Ingen distraksjonar.
+
+
+ Kom i gang
+
+
+
+ %1$s er ikkje som andre nettlesarar
+
+
+ Vi slettar historikken din for ekstra personvern, når du lét att appen.
+
+
+
+ Gjer %1$s til din standard-nettlesar for å beskytte dataa dine, kvar gong du opnar ei lenke.
+
+
+ Bruk som standard nettleser
+
+
+ Hopp over
+
+
+
+ Styrk personvernet ditt
+
+ Ta privat nettlesing til neste nivå. Blokker annonsar og anna innhald som kan spora deg på tvers av nettstadar og som kan gjera nedlasting av nettsider tregare.
+
+
+ Ditt søk på din måte
+
+ Søkjer du etter noko anna? Vel ein annan standard-søkjemotor i Innstillingar.
+
+
+ Legg til snarvegar på startskjermen
+
+ Gå raskt tilbake til favorittnettstadane dine i %1$s. Vel «Legg til på startskjermen» frå %1$s-menyen.
+
+
+ Gjer personvern til ein vane
+
+ Vel %1$s som standardnettleser, og få fordelane med privat nettlesing når du opnar nettsider frå andre apper.
+
+ OK, eg forstår det!
+ Hopp over
+ Neste
+
+
+ -
+
+
+ Legg til
+
+
+ JA
+
+
+ Avbryt
+
+
+ NEI
+
+
+ Snarvegen vil bli opna med utvida sporingsvern avslått
+
+
+ Privat nettlesingsøkt
+
+
+ Med varsel kan du sletta økta di i %1$s med eitt klikk. Du treng ikkje å opna appen, eller å sjå kva som køyrer i nettlesaren din.
+
+
+ Slett nettlesarhistorikk
+
+
+ Last ned Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License og andre lisensar for open kjeldekode.]]>
+
+
+ her.]]>
+
+
+ lisensar.]]>
+
+
+ GNU General Public License v3, og er tilgjengeleg her .]]>
+
+
+ Brukarnamn
+ Passord
+ Tøm
+
+
+
+ Trygg tilkopling
+ Utrygg tilkopling
+
+ Stadfesta av: %1$s
+
+
+ Nettstadsikkerheit
+ URL-en finst allereie
+
+
+ Søk på sida
+
+
+ Søk på sida
+
+
+ %1$d/%2$d
+
+ %1$d av %2$d
+
+
+ Søk etter neste
+
+ Søk etter førre
+
+ Avvis søk på sida
+
+
+
+
+ Vis skrivebordsversjon
+
+
+ Datamaskinversjon
+
+
+ URL kopiert
+
+
+ Utviklarverktøy
+
+
+ Opne lenker i appar
+
+
+ Avansert
+
+
+ Nettstadløyve
+
+
+ Redusering av infokapselbanner
+
+
+ På
+
+
+ Av
+
+
+ Redusering av infokapselbanner
+
+
+ Sjå færre banner ved å automatisk avvise førespurnadar om infokapslar, når det er muleg.
+
+ -->
+ Redusering av infokapselbanner
+
+
+ PÅ for denne nettstaden
+
+
+ Nettstaden er for augneblinken ikkje støtta
+
+
+ AV for denne nettstaden
+
+
+ Redusering av infokapselbanner
+
+
+ AV for denne nettstaden
+
+
+ PÅ for denne nettstaden
+
+
+ Vill du aktivere reduksjon av infokapselbanner for %1$s?
+
+
+ Vil du deaktivere reduksjon av infokapselbanner for %1$s?
+
+
+ %1$s vill slette infokapslar og oppdatere sida. Sletting av alle infokapslar kan føre til at du blir logga ut eller at handlekorger vert tømde.
+
+
+ %1$s kan prøve å automatisk avvise infokapselførespurnadar.
+
+
+ Denne nettstaden er for tida ikkje støtta av Redusering av infokapselbanner. Vil du be om at teamet vårt vurderer denne nettsida og støttar henne i framtida?
+
+
+ Avbryt
+
+
+ Be om brukarstøtte
+
+
+ Førespurnad om støtte av nettstad er sendt inn.
+
+
+ Førespurnad om støtte av nettstad er sendt inn.
+
+
+
+ %1$s prøver å avvise infokapsellførespurnadar for å avvise irriterande infokapselbanner.\n\nAdministrer innstillingar for infokapselbanner i %2$s.
+
+
+ innstillingar
+
+
+ Automatisk avspeling
+
+
+ For å tillate det:
+
+
+ 1. Gå til Android-innstillingar
+
+
+ Løyve]]>
+
+
+ Gå til Innstillingar
+
+
+ %1$s PÅ]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Plassering
+
+
+ Varsel
+
+
+ DRM-kontrollert innhald
+
+
+ Spør om løyve
+
+
+ Blokkert
+
+
+ Tillate
+
+
+ Blokkert av Android
+
+
+ Tillat lyd og video
+
+
+ Blokker berre lyd
+
+
+ Tilrådd
+
+
+ Blokker lyd og video
+
+
+ Undersøkingar
+
+
+ Firefox kan installere og køyre undersøkingar frå tid til anna.
+
+
+ Les meir
+
+
+ Applikasjonen vil avslutte for å setje i verk endringar
+
+
+ Fjern
+
+
+ Aktiv
+
+
+ Fullført
+
+
+ Ekstern debugging via USB/Wi-Fi
+
+
+ Lås opp
+
+
+ Stadfest ved bruk av fingeravtrykket ditt
+
+
+ Du kan bruke fingeravtrykket for å fortsetje denne appøkta.
+
+
+ Opne lenke i ny økt
+
+
+ Fingeravtrykkikon
+
+
+ Fingeravtrykk ikkje identifisert. Prøv igjen.
+
+
+ Fingeren rørte seg for fort. Prøv igjen.
+
+
+ Vise søkjeforslag?
+
+
+ For å få forslag må %1$s sende det du skriv i adressefeltet til søkjemotoren.
+
+
+ Nei
+
+
+ Ja
+
+
+ Nokre søkjemotorar kan ikkje vise forslag.
+
+
+ Ignorer
+
+
+
+
+ Oppfører sida seg uventa?\n Prøv å deaktivera sporingsvern
+
+
+ Legg til på startskjermen]]>
+
+
+ Opna kvar lenke i %1$s\n Vel %1$s som standardnettlesar
+
+
+ Autofullfør nettadresser for nettstadar du brukar mest\n Trykk og hald på ei nettadresse i adressefeltet
+
+
+ Opna ei lenke i ei ny fane\n Trykk og hald på ei lenke på ei side
+
+
+ Slå av tips på startskjermen
+
+
+ Ny fane opna
+
+
+ Byt
+
+
+ Startar fullskjermmodus
+
+
+ Byt til lenke i ny fane med ein gong
+
+
+ Blokker potensielt farlege og villeiande nettsider
+
+ Blokker rapporterte villeiande- og angrepsnettsider, skadeprogram-nettsider og uønskte programvarenettsider.
+
+
+ Berre HTTPS-modus
+
+
+ Prøver automatisk å kople til nettstadar ved hjelp av HTTPS-krypteringsprotokollen for auka sikkerheit.
+
+
+ Unntak
+
+ Du har slått av innhaldsblokkering for desse nettsidene.
+
+ Fjern
+
+ Fjern alle nettstadar
+
+
+ Blokker infokapslar
+
+
+ Vil du blokkere infokapslar?
+
+
+ Fana krasja
+
+ Orsak. Vi har eit problem med denne fana.
+
+ Som ein privat nettlesar lagrar vi aldri og kan ikkje gjenopprette denne fana.
+
+ Lat att fane
+
+
+
+
+
+ Send krasjrapport til Mozilla
+
+
+
+
+ Sporarar blokkert sidan %s
+
+ Innhald
+
+
+ Reklame
+
+ Sosialt
+
+ Analyse
+
+
+ Utvida sporingsvern
+
+ Vern er slått AV for denne nettstaden
+
+ Vern er slått PÅ for denne nettstaden
+
+
+ Tilkoplinga er trygg
+
+ Tilkoplinga er ikkje trygg
+
+ Sporarar og skript å blokkere
+
+
+ Gå tilbake
+
+
+
+ Fjern
+
+
+ Byt namn
+
+ Gi nytt namn
+
+
+ Namn på snarveg
+
+
+ Lagra og delte bilde <b>vil ikkje bli</b> fjerna når du slettar %1$s-historikken
+
+
+
+ Tema
+
+ Lyst
+
+ Mørkt
+
+ Innstilt av batterisparing
+
+ Følg tema til eining
+
+
+
+ Denne nettstaden støttar ikkje HTTPS
+
+
+ Les meir
+ Endre denne innstillinga i Instillingar > Personvern og sikkerheit > Sikkerheit.]]>
+
+
+ Tilkoplinga er ikkje trygg
+
+
+
+ Om du har kopla til denne serveren tidlegare kan feilen vere tilfeldig.
+ ]]>
+
+
+ Nokon kan prøve å gje seg ut for å vere nettstaden, og å halde fram kan vere riskabelt.
+
+ %1$s stolar ikkje på %2$s fordi sertifikatutskrivaren er ukjend, sertifikatet er sjølvsignert eller at serveren ikkje sender rett mellomliggjande sertifikat.
+ ]]>
+
+
+
+ Lat att fane
+
+
+
+ Tatt på fersk gjerning! Vi hindra denne nettstaden i å spionere på deg. Trykk på skjoldet når som helst for å sjå kva vi blokerer.
+
+
+ Lat att sprettoppvindauge
+
+
+
+ Du er verna!
+
+ Desse standardinnstillingane gir sterkt vern. Men det er enkelt å tilpasse innstillingane for å møte dine spesielle behov.
+
+ Avvis
+
+
+ Trykk her for å kvitte deg med alt — historikk, infokapslar, alt — for å byrje på nytt på ei ny fane.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Lat att
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Søkje-widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Nettlesarhistorikk er sletta! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Start den private nettlesingsøkta di, så blokkerer vi sporarar og andre dårlege ting medan du surfar.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Vi overlèt deg til den private nettlesinga di, men får ein raskare start neste gong med %1$s-widgeten på startskjermen.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Legg til widget på startskjermen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget lagt til på startskjermen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-nv/strings.xml b/mobile/android/focus-android/app/src/main/res/values-nv/strings.xml
new file mode 100644
index 0000000000..3b2393a59a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-nv/strings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+ Dooda nisin
+ Lą́\'ąą
+
+ Hanítą́ \'éí doodaii\' addressígíí biih níłtązh
+
+ T\'áá bí nanil\'ingo hainitá.
+
+ Hanítá baa hane\' yę́ę \'ígíí nits\'ą́ą́\' k\'éé\'óólchxǫ\'.
+
+ Hanį́tá %1$s
+
+ "Ła\' bił \'ííshjání \'ádeeshłííł nisin "
+
+ Siteígíí ba\'at\'e\' hólǫ́ léi\' \'áko \'éí baa hodeeshnih nisin
+
+ Tsésǫ\' \'ániidígíí léi\' biyi\'jį\' \'ąą \'ádeeshłííł nisin %1$s.
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-oc/strings.xml b/mobile/android/focus-android/app/src/main/res/values-oc/strings.xml
new file mode 100644
index 0000000000..50541ea88c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-oc/strings.xml
@@ -0,0 +1,1114 @@
+
+
+
+
+
+
+
+
+ Anullar
+
+ D\'acòrdi
+
+ Enregistrar
+
+
+ Picar un tèrme de recercar o una adreça
+
+ Navegacion privada automatica.\nNavegatz. Escafatz. Tornatz-i.
+
+
+ Vòstre istoric de navegacion es estat escafat.
+ Istoric de navegacion escafat
+
+
+ L’istoric de navegacion de l’onglet es estat escafat.
+
+
+ Recercar %1$s
+
+
+ Partejar…
+
+
+ Senhalar un problèma sul site
+
+
+ Dobrir amb %1$s
+
+
+ Dobrir amb…
+
+
+ Apondre a l’ecran d’acuèlh
+
+
+ Apondre als acorchis
+
+ Suprimir dels acorchis
+
+
+ Paramètres
+ A prepaus
+ Ajuda
+ Vòstres dreches
+
+
+ Traçadors blocats
+
+
+ Desactivar aquesta option pòt reglar unes problèmas del site
+
+
+ Blocatge de contengut
+
+ Desactivar per petaçar unes sites
+
+
+ Fonciona gràcia a %1$s
+
+
+ Partejar via
+
+ Escafar l’istoric de navegacion ?
+ Tocatz aquesta notificacion o suprimissètz-la per escafar l’istoric de navegacion d’una manièra segura.
+
+
+ Tocatz o lisatz aquesta notificacion per escafar l’istoric de navegacion d’una manièra segura.
+
+ Netejar l’istoric de navegacion
+
+
+ Dobrir
+
+
+ Escafar e dobrir
+
+
+ Escafar
+
+
+ Escafar l\'istoric de navegacion
+
+
+
+ Escafar e dobrir
+
+
+ Escafar e dobrir %1$s
+
+
+
+ Recercar dins Focus
+
+ Recercar dins Klar
+
+ Recercar dins Focus Beta
+
+ Recercar dins Focus Nightly
+
+
+ %1$s vos dòna lo contraròtla.
+Utiliza-lo coma un navegador privat :
+
+ Recercatz e navegatz dins l’aplicacion
+ Blocatz los traçadors (o cambiatz lo paramètre per autorizar los traçadors)
+ Escafatz los cookies e los istorics de recèrcas e navegacion
+
+
+%1$s es produch per Mozilla. Nòstra mission es d’encoratjar un Internet en bona santat e dobèrt.
+Ne saber mai
]]>
+
+
+ Vida privada e seguretat
+
+
+ Seguiment, cookies, donadas reculhidas
+
+
+ Motor per defaut, autocompletacion
+
+
+
+
+ A prepaus de %1$s, ajuda
+
+
+ Proteccion contra lo seguiment renfortida
+
+
+ Contengut web
+
+
+ Bascula entre las aplicacions
+
+
+ General
+
+
+ Navegador per defaut, lenga
+
+
+ Collecta de donadas e utilizacion
+
+ Recèrca
+
+
+ Mostrar las suggestions de recèrca
+
+ %1$s enviarà çò que picatz dins la barra d’adreça al motor de recèrca
+
+
+ Per defaut
+
+
+ Motor de recèrca
+
+
+ Activat
+
+
+ Desactivat
+
+
+ Emplenament d’adreças
+
+
+ Pels sites favorits
+
+
+ Activar per que %s complete la barra d’adreça amb 450 adreças mai popularas.
+
+
+ Pels sites qu’apondètz
+
+
+ Activar per que %s complete vòstras adreças favoritas.
+
+
+ Gerir los sites
+
+
+ Gerir los sites
+
+
+ + Ajustar una adreça personalizada
+
+
+ Vòstra lista d’auto emplenatge :
+
+
+ Apondre l’adreça
+
+
+ Ajustar una adreça personalizada
+
+
+ Ajustar una adreça personalizada
+
+
+ Apondre un ligam per autocompletar
+
+
+ Cookies e donadas de sites
+
+
+ Donadas reculhidas
+
+
+ Levar d’adreças personalizadas
+
+
+ Ne saber mai
+
+
+ Apondre e gerir las adreças personalizadas per las completar automaticament.
+
+
+ Adreça d\'apondre
+
+
+ Pegar o picar una adreça web
+
+
+ Exemple : mozilla.org
+
+
+ Exemple : exemple.com
+
+
+ Novèla adreça personalizada ajustada.
+
+
+ Suprimir
+
+
+ Suprimir
+
+
+ Mercés de verificar las adreças picadas.
+
+ Lenga
+
+ Valors per defaut del sistèma
+
+ Vida privada
+ "Blocar los traçadors publicitaris "
+ De publicitats vos seguèsson, amai las cliquetz pas
+ Blocar los traçadors d’estatisticas
+ Servisson a reculhir, analisar e mesurar las activitats coma la tòca o defilament
+ Blocar los traçadors de malhums socials
+ Son embarcats dins de sites per traçar vòstras visitas e mostrar de foncionalitat coma los botons de partatge
+ Blocar los autres traçadors de contengut
+ Activar aquesta opcion pòt provocar de problèmas sus de paginas
+ Blocar los cookies
+
+
+ Non, mercé
+ Blocar solament los traçadors tèrces
+ Blocar solament los cookies tèrces
+ Blocar los cookies intersites
+ Òc
+
+
+ Utilizar una emprenta digitala per desblocar l’aplicacion
+
+
+ Desverrolhatz via la detada s’apondèretz d’acorchis o quand un site web es ja dobèrt dins %s.
+
+
+ Invisible
+
+ Amagar las paginas web en cambiar d’aplicacions e blocar la presa de capturas d’ecran.
+
+ Seguretat
+
+ Performanças
+ Blocar las polissas Web
+
+ Pòt far mancar d’imatges o icònas
+
+ Blocar JavaScript
+
+ Las paginas pòdon se cargar mai rapidament, mas pòdon se comportar pas coma cal
+
+
+ Far que %1$s siá lo navegador per defaut
+
+ Mozilla
+ Mandar de donadas d’utilizacion
+
+
+ Ne saber mai
+
+
+ Mozilla s’esfòrça de reculhir pas que las donadas necessàrias per vos ofrir e melhorar %1$s.
+
+
+ Politica de confidencialitat
+
+
+ Entresenhas de licéncia
+
+
+ Bibliotècas qu’utilizam
+
+
+ %s | Bibliotècas liuras
+
+
+ A prepaus de %1$s
+
+
+ Motors de recèrca installats
+
+
+ Causir lo motor de recèrca
+
+
+ Restablir los motors de recèrca per defaut
+
+
+ + Apondre un motor de recèrca mai
+ Levar de motors de recèrca
+ Levar
+
+ Apondre un motor de recèrca de mai
+
+ Causissètz vòstre motor preferit :
+
+
+ Apondre un motor de recèrca
+
+ Nom del motor de recèrca
+ Motor de recèrca d’emplegar
+ Enregistrar
+
+
+ Exemple : exemple.com/search/?q=%s
+
+ Novèl motor de recèrca apondut.
+
+ Picatz lo nom del novèl motor de recèrca
+ Un motor de recèrca emplega ja aqueste nom.
+
+ Picar una cadena de recèrca
+
+ Verificatz que la cadena de recèrca siá al format de l’exemple
+
+
+ Escafar lo tèxte
+
+
+ Remandar
+
+
+ Netejar l’istoric de navegacion
+
+
+ Onglet dobèrts : %1$s
+
+
+ Connexion segura
+
+
+ Cargament
+
+
+ Site web cargat
+
+
+ Mai d\'opcions
+
+
+ Boton per mostrar mai d’opcions
+
+
+ Avançar dins l’istoric
+
+
+ Actualizar la pagina
+
+
+ Tornar
+
+
+ Quitar de cargar lo site web
+
+
+ Tornar a l’aplicacion precedenta
+
+
+ Nombre de traçadors blocats
+
+
+ Blocar los traçadors
+
+ Vòstres dreches
+
+ Dobrir lo ligam dins una autra aplicacion
+
+ Sètz per quitar %1$s per dobrir aqueste ligam dins %2$s.
+
+ Trobar una aplicacion per dobrir aqueste ligam
+
+ Cap de las aplicacions de l’ordenador pòt pas dobrir aqueste ligam. Podètz quitar %1$s per cercar dins %2$s una aplicacion que pòt lo dobrir.
+
+ Sortir de la navegacion privada ?
+
+
+ %1$s acabat
+
+
+ Dobrir
+
+
+
+
+
+
+
+
+
+
+ Apondut als acorchis !
+
+ Impossible de trobar lo servidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tampar
+
+
+
+ La benvenguda a %1$s
+
+
+ Rapid. Confidencial. Sens distraccion.
+
+
+ Per començar
+
+
+
+ %1$s es pas coma los autres navegadors
+
+
+ Escafam l’istoric quand tampatz l’aplicacion per mai de confidencialitat.
+
+
+
+ Fasètz venir %1$s vòstre navegador per defaut per protegir vòstras donadas per cada ligam que dobrissètz.
+
+
+ Definir coma navegador per defaut
+
+
+ Passar
+
+
+
+ Renfortissètz vòstra vida privada
+
+ Passatz a un nivèl mai naut de navegacion privada. Oblidetz las reclamas e los autres contenguts que pòdon vos traçar de sites en sites e rendon pesucas las paginas.
+
+
+ Recercatz de vòstre biais
+
+ Cercatz quicòm de diferent ? Causissètz un autre motor de recèrca per defaut als Paramètres.
+
+
+ Apondre d’acorchis a vòstre ecran d’acuèlh
+
+ Tornatz rapidament a vòstres sites favorits dins %1$s. Causissètz «Apondre a l’ecran d’acuèlh» al menú de %1$s.
+
+
+ Vòstra vida priva a vòstras mans
+
+ Fasètz de %1$s vòstre navegador per defaut e aprofeitatz d’una navegacion privada tre que dobrissètz una pagina web d’una autras aplicacions.
+
+ Comprés !
+ Passar
+ Seguent
+
+
+ -
+
+
+ Apondre
+
+ ÒC
+
+
+ Anullar
+
+ NON
+
+
+ Aqueste acorchi se dobrirà amb proteccion renfortida contra lo seguiment desactivada
+
+
+ Navegacion privada
+
+
+ Las notificacions permeton de suprimir la session de %1$s amb una tòca. Cal pas dobrir l’aplicacion nimai mostrar lo contengut del navegador.
+
+
+ Netejar l’istoric de navegacion
+
+
+ Telecargar Firefox
+
+
+
+
+
+
+
+
+ Licéncia publica de Mozilla e d’autras licéncias open source.]]>
+
+
+ aquí.]]>
+
+
+ licéncias liuras e open source.]]>
+
+
+ GNU General Public License v3, e disponiblas aquí .]]>
+
+
+ Nom d\'utilizaire
+ Senhal
+ Escafar
+
+
+
+ Connexion securizada
+ Connexion pas segura
+
+ Verificat per : %1$s
+
+
+ Seguretat del site
+ L’adreça existís ja
+
+
+ Recercar dins la pagina
+
+
+ Recercar dins la pagina
+
+
+ %1$d/%2$d
+
+ %1$d demest %2$d
+
+
+ Trobar lo resultat seguent
+
+ Trobar lo resultat precedent
+
+ Quitar la recèrca dins la pagina
+
+
+
+
+ Veire version ordenador
+
+
+ Version ordenador
+
+
+ URL copiada
+
+
+ Aisinas pels desvolopaires
+
+
+ Dobrir los ligams dins las aplicacions
+
+
+ Avançat
+
+
+ Autorizacions de site
+
+
+ Reduccion de las bandièras de cookies
+
+
+ Activada
+
+
+ Desactivada
+
+
+ Reduccion de las bandièras de cookies
+
+
+ Veire mens banièras en regetant automaticament las demandas de cookies, quand es possible.
+
+ -->
+ Reduccion de las bandièras de cookies
+
+
+ Activada per aqueste site
+
+
+ Site actualament pas pres en carga
+
+
+ Desactivada per aqueste site
+
+
+ Reduccion de las bandièras de cookies
+
+
+ Desactivada per aqueste site
+
+
+ Activada per aqueste site
+
+
+ Activar la reduccion de las bandièras de cookies per %1$s ?
+
+
+ Desactivar la reduccion de las bandièras de cookies per %1$s ?
+
+
+ %1$s escafarà los cookies d’aqueste site e actualizarà la pagina. La supression de totes los cookies pòt vos desconnectar o voidar los panièrs de crompa.
+
+
+ %1$s pòt ensajar de refusar automaticament las demandas de cookies.
+
+
+ Aqueste site es actualament pas pres en carga per la reduccion de banièras de cookies. Volètz demandar a nòstra còla de verificar aqueste site e d’apondre sa compatibilitat pel futur ?
+
+
+ Anullar
+
+
+ Demandar sa presa en carga
+
+
+ La demanda de presa en carga es estada enviada.
+
+
+ La demanda de presa en carga es estada enviada.
+
+
+
+ %1$s ensaja de regetar las demandas de cookies per ignorar las bandièras de cookies embestiantas.\n\n Gerir preferéncias de bandièras de cookies a las preferéncias de %2$s.
+
+
+ paramètres
+
+
+ Lectura automatica
+
+
+ Per l’autorizar :
+
+
+ 1. Anar als paramètres Android
+
+
+ Permissions]]>
+
+
+ Anar als paramètres
+
+
+ %1$s sus ACTIVAT]]>
+
+
+ Camèra
+
+
+ Microfòn
+
+
+ Emplaçament
+
+
+ Notificacion
+
+
+ Contengut contrarotlat per DRM
+
+
+ Demandar per autorizar
+
+
+ Blocat
+
+
+ Autorizat
+
+
+ Blocat per Android
+
+
+ Autorizar l’àudio e la vidèo
+
+
+ Blocar sonque l’àudio
+
+
+ Recomandat
+
+
+ Blocar l’àudio e la vidèo
+
+
+ Estudis
+
+
+ Firefox pòt installar e lançar d’estudis de temps en temps.
+
+
+ Ne saber mai
+
+
+ L’aplicacion es a se tampar per aplicar las modificacions
+
+
+ Suprimir
+
+
+ Actiu
+
+
+ Acabats
+
+
+ Desbugatge alonhat per USB/Wi-Fi
+
+
+ Desverrolhar
+
+
+ Confirmatz via vòstra emprenta digitala
+
+
+ Podètz utilizar vòstra emprenta digitala per contunhar la session d’aplicacion actuala.
+
+
+ Dobrir lo ligam dins una session novèla
+
+
+ Icòna d’emprentas digitalas
+
+
+ Detadas pas reconegudas. Tornatz ensajar.
+
+
+ Lo det a bolegat tròp rapidament. Tornatz ensajar.
+
+
+ Afichar las suggestions de recèrca ?
+
+
+ Per obténer de suggestions, %1$s a d’enviar al motor de recèrca çò que picatz dins la barra d’adreça.
+
+
+ Non
+
+
+ Òc
+
+
+ Unes motor de recèrca pòdon pas mostrar de suggestions.
+
+
+ Tampar
+
+
+
+
+ Aqueste site a un compòrtament inesperat ?\n Ensajatz de desactivar la proteccion contra lo seguiment
+
+
+ Apondre a l’ecran d’acuèlh]]>
+
+
+ Dobrir totes los ligams amb %1$s\n Definir %1$s coma navegador per defaut
+
+
+ Emplenament automaticament de las URL dels sites mai visitats\n Mantenètz quichat quin que siá URL dins la barra d’adreça
+
+
+ Dobrir un ligam dins un onglet novèl\n Mantenètz quichat quin que siá ligam de la pagina
+
+
+ Quitar de mostrar las astúcias a l’ecran de començament
+
+
+ Novèl onglet dobèrt
+
+
+ Mostrar
+
+
+ Mòde plen ecran actiu
+
+
+ Mostrar sulcòp los ligams dobèrts
+
+
+ Blocar los sites potencialament perilhoses e enganaires
+
+ Blocar los sites senhalats coma perilhoses, los sites de logicials dangieroses e los sites de logicials pas desirats.
+
+
+ Mòde HTTPS solament
+
+
+ Ensajar de se connectar automaticament als sites amb lo chiframent HTTPS per una seguretat melhorada.
+
+
+ Excepcions
+
+ Avètz desactivat lo blocatge de contengut per aquestes sites.
+
+ Suprimir
+
+ Suprimir totes los sites web
+
+
+ Blocar los cookies
+
+
+ Volètz blocar los cookies ?
+
+
+ L’onglet a plantat
+
+ O planhèm, avèm de problèmas amb aqueste onglet.
+
+ Coma navegador privat, enregistram pas jamai res e podèm pas restablir aqueste onglet.
+
+ Tampar l\'onglet
+
+
+
+
+
+ Enviar un senhalament de bug a Mozilla
+
+
+
+
+ Traçadors blocats dempuèi lo %s
+
+ Contengut
+
+ Publicitat
+
+ Ret social
+
+ Estatisticas
+
+ Proteccion contra lo seguiment renfortida
+
+ Las proteccions son desactivadas per aqueste site
+
+ Las proteccions son activadas per aqueste site
+
+ La connexion es segura
+
+ La connexion es pas segura
+
+ Traçadors e scripts de blocar
+
+
+ Tornar
+
+
+
+ Suprimir
+
+
+ Renommar
+
+ Renommar
+
+
+ Nom de l’acorchi
+
+
+ Los imatges salvagardats o partejats <b>seràn pas</b> suprimits en netejar vòstre istoric %1$s.
+
+
+
+ Tèma
+
+ Clar
+
+ Escur
+
+ Definit per l’estalviador de batariá
+
+ Segon lo tèma del periferic
+
+
+
+ Aqueste site es pas compatible HTTPS
+
+
+ Ne saber mai
+ Cambiatz aqueste paramètre dins Paramètres > Vida privada e seguretat > Seguretat.]]>
+
+
+ Connexion pas segura
+
+
+
+ Se ja sètz connectat(ada) amb succès a aqueste servidor, benlèu l’error es temporària e podètz ensajar tornamai pus tard.
+ ]]>
+
+
+ Qualqu’un poiriá usurpar l’identitat del site, contunhar la navegacion poiriá èsser perilhós.
+
+ Los sites web pròvan lor identitat via de certificats. %1$s se fisa pas de %2$s perque lo seu emissor de certificats es desconegut, lo certificat es auto-signat, o lo servidor envia pas los certificats intermediari corrèctes. ]]>
+
+
+
+ Tampar l’onglet
+
+
+
+ Atrapat ! Avèm empachat aqueste site de vos pistar. Tocatz l’escut quand volètz per saber çò qu’avèm blocat.
+
+
+ Tampar lo fenestron
+
+
+
+ Sètz protegit !
+
+ Aqueste paramètres per defaut ofrisson una proteccion renfortida. Mas es facil de modificar los paramètres per los far correspondre a vòstres besonhs.
+
+ Ignorar
+
+
+ Tocatz aicí per tot escafar — istoric, cookies, tot — e recomençar dins un onglet novèl.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Tampar
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget de recèrca
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Istoric de navegacion escafat ! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Començatz una session de navegacion privada e blocarem los traçadors e autras marridas causas d’aqueste temps.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Vos anam daissar navegar en privat, mas lo còp que ven, començat mai rapidament amb un widget %1$s per l’ecran d’acuèlh.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Apondre lo widget a l’ecran d’acuèlh
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget apondut a l’ecran d’acuèlh
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-pa-rIN/strings.xml b/mobile/android/focus-android/app/src/main/res/values-pa-rIN/strings.xml
new file mode 100644
index 0000000000..e036343bb9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-pa-rIN/strings.xml
@@ -0,0 +1,1118 @@
+
+
+
+
+
+
+
+
+ ਰੱਦ ਕਰੋ
+
+ ਠੀਕ ਹੈ
+
+ ਸੰਭਾਲੋ
+
+
+ ਖੋਜੋ ਜਾਂ ਸਿਰਨਾਵਾਂ ਦਿਓ
+
+ ਆਪਣੇ-ਆਪ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ।\nਬਰਾਊਜ਼ ਕਰੋ। ਸਾਫ਼ ਕਰੋ। ਦੁਹਰਾਓ।
+
+
+ ਤੁਹਾਡੇ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਸਾਫ਼ ਕਰ ਦਿੱਤਾ ਜਾਵੇਗਾ।
+ ਬਰਾਊਜਿੰਗ ਅਤੀਤ ਨੂੰ ਮਿਟਾਇਆ
+
+
+ ਟੈਬ ਦੇ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਮਿਟਾਇਆ ਜਾ ਚੁੱਕਾ ਹੈ।
+
+
+ %1$s ਲਈ ਖੋਜੋ
+
+
+ …ਸਾਂਝਾ ਕਰੋ
+
+
+ ਸਾਈਟ ਮਸਲੇ ਦੀ ਰਿਪੋਰਟ ਕਰੋ
+
+
+ %1$s ਵਿੱਚ ਖੋਲ੍ਹੋ
+
+
+ …ਵਿੱਚ ਖੋਲ੍ਹੋ
+
+
+ ਮੁੱਖ ਸਕਰੀਨ \'ਤੇ ਜੋੜੋ
+
+
+ ਸ਼ਾਰਟਕੱਟਾਂ ਵਿੱਚ ਜੋੜੋ
+
+
+ ਸ਼ਾਰਟਕੱਟ ਵਿੱਚੋਂ ਹਟਾਓ
+
+
+ ਸੈਟਿੰਗਾਂ
+ ਇਸ ਬਾਰੇ
+ ਮਦਦ
+ ਤੁਹਾਡੇ ਹੱਕ
+
+
+ ਟਰੈਕਰਾਂ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਈ
+
+
+ ਇਸ ਨੂੰ ਬੰਦ ਕਰਨ ਨਾਲ ਕੁਝ ਸਾਈਟਾਂ ਦੀਆਂ ਸਮੱਸਿਆਵਾਂ ਠੀਕ ਹੋ ਸਕਦੀਆਂ ਹਨ
+
+
+ ਸਮੱਗਰੀ ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+ ਕੁਝ ਸਾਈਟਾਂ ਠੀਕ ਕਰਨ ਲਈ ਬੰਦ ਕਰੋ
+
+
+ %1$s ਰਾਹੀਂ ਤਿਆਰ ਕੀਤਾ
+
+
+ ਇਸ ਰਾਹੀਂ ਸਾਂਝਾ ਕਰੋ
+
+ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਮਿਟਾਉਣਾ ਹੈ?
+ ਤੁਹਾਡੇ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਮਿਟਾਉਣ ਲਈ ਇਸ ਨੋਟੀਫਿਕੇਸ਼ਨ ਨੂੰ ਛੂਹੋ ਜਾਂ ਸਾਫ਼ ਕਰੋ।
+
+
+ ਤੁਹਾਡੇ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਮਿਟਾਉਣ ਲਈ ਇਸ ਨੋਟੀਫਿਕੇਸ਼ਨ ਨੂੰ ਛੂਹੋ ਜਾਂ ਸਰਕਾਓ।
+
+ ਬਰਾਊਜ਼ਰ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਮਿਟਾਓ
+
+
+ ਖੋਲ੍ਹੋ
+
+
+ ਮਿਟਾਓ ਅਤੇ ਖੋਲ੍ਹੋ
+
+
+ ਮਿਟਾਓ
+
+
+ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਨੂੰ ਮਿਟਾਓ
+
+
+
+ ਮਿਟਾਓ ਤੇ ਖੋਲ੍ਹੋ
+
+
+ ਮਿਟਾਓ ਅਤੇ %1$s ਖੋਲ੍ਹੋ
+
+
+
+ Focus ਵਿੱਚ ਲੱਭੋ
+
+ Klar ਵਿੱਚ ਲੱਭੋ
+
+ Focus Beta ਵਿੱਚ ਲੱਭੋ
+
+ Focus Nightly ਵਿੱਚ ਲੱਭੋ
+
+
+ %1$s ਕੰਟਰੋਲ ਤੁਹਾਡੇ ਹੱਥ ਦਿੰਦਾ ਹੈ।
+ਇਸ ਨੂੰ ਨਿੱਜੀ ਬਰਾਊਜ਼ਰ ਵਜੋਂ ਵਰਤੋਂ:
+
+ ਐਪ ਦੇ ਵਿੱਚ ਖੋਲ੍ਹੋ ਅਤੇ ਬਰਾਊਜ਼ ਕਰੋp
+ ਟਰੈਕਰਾਂ ਤੇ ਲਾਓ ਪਾਬੰਦੀ (ਜਾਂ ਟਰੈਕਰਾਂ ਨੂੰ ਇਜਾਜ਼ਤ ਦੇਣ ਲਈ ਸੈਟਿੰਗਾਂ ਅੱਪਡੇਟ ਕਰੋ)
+ ਖੋਜ ਅਤੇ ਬਰਾਊਜ਼ ਕਰਨ ਦੇ ਅਤੀਤ ਦੇ ਨਾਲ ਨਾਲ ਕੂਕੀਜ਼ ਨੂੰ ਹਟਾਉਣ ਲਈ ਮਿਟਾਓ
+
+
+%1$s ਨੂੰ Mozilla ਨੇ ਬਣਾਇਆ ਹੈ। ਸਾਡਾ ਮਕਸਦ ਚੰਗਾ, ਆਜ਼ਾਦ ਸਰੋਤ ਇੰਟਰਨੈੱਟ ਖੜ੍ਹਾ ਕਰਨਾ ਹੈ।
+ਹੋਰ ਜਾਣੋ
]]>
+
+
+ ਪਰਦੇਦਾਰੀ ਤੇ ਸੁਰੱਖਿਆ
+
+
+ ਟਰੈਕਿੰਗ, ਕੂਕੀਜ਼, ਡਾਟਾ ਚੋਣਾਂ
+
+
+ ਮੂਲ ਸੈੱਟ ਕਰੋ, ਆਪੇ-ਪੂਰਨ ਕਰੋ
+
+
+
+
+ %1$s ਬਾਰੇ, ਮਦਦ
+
+
+ ਵਾਧਾ ਕੀਤੀ ਟਰੈਕਿੰਗ ਸੁਰੱਖਿਆ
+
+
+ ਵੈੱਬ ਸਮੱਗਰੀ
+
+
+ ਐਪਾਂ ਵਿੱਚ ਤਬਦੀਲ ਕਰਨਾ
+
+
+ ਆਮ
+
+
+ ਮੂਲ ਬਰਾਊਜ਼ਰ, ਭਾਸ਼ਾ
+
+
+ ਡਾਟਾ ਇਕੱਤਰ ਅਤੇ ਵਰਤੋਂ
+
+ ਖੋਜੋ
+
+
+ ਖੋਜ ਸੁਝਾਅ ਲਵੋ
+
+ %1$s ਤੁਹਾਡੇ ਖੋਜ ਇੰਜਣ ਨੂੰ ਸਿਰਨਾਵਾਂ ਪੱਟੀ ਵਿੱਚ ਤੁਹਾਡੇ ਵਲੋਂ ਲਿਖਤ ਭੇਜੀ ਜਾਵੇਗੀ
+
+
+ ਮੂਲ
+
+
+ ਖੋਜ ਇੰਜਣ
+
+
+ ਚਾਲੂ
+
+
+ ਬੰਦ
+
+
+ URL ਆਪੇ-ਪੂਰਨ
+
+
+ ਚੋਟੀ ਦੀਆਂ ਸਾਈਟਾਂ ਲਈ
+
+
+ %s ਨੂੰ ਸਿਰਨਾਵਾਂ ਪੱਟੀ ਵਿੱਚ 450 ਹਰਮਨਪਿਆਰੇ URL ਆਪਣੇ-ਆਪ ਪੂਰੇ ਕਰਨ ਦੇ ਸਮਰੱਥ ਬਣਾਓ।
+
+
+ ਤੁਹਾਡੇ ਵਲੋਂ ਜੋੜਨ ਲਈ ਸਾਈਟਾਂ ਵਾਸਤੇ
+
+
+ %s ਨੂੰ ਤੁਹਾਡੇ ਮਨਪਸੰਦ URL ਆਪਣੇ-ਆਪ ਪੂਰੇ ਕਰਨ ਦੇ ਸਮਰੱਥ ਬਣਾਓ।
+
+
+ ਸਾਈਟਾਂ ਦਾ ਬੰਦੋਬਸਤ ਕਰੋ
+
+
+ ਸਾਈਟਾਂ ਦਾ ਬੰਦੋਬਸਤ ਕਰੋ
+
+
+ + ਕਸਟਮ URL ਜੋੜੋ
+
+
+ ਤੁਹਾਡੀ ਆਪੇ-ਪੂਰਨ ਸੂਚੀ:
+
+
+ URL ਜੋੜੋ
+
+
+ ਕਸਟਮ URL ਜੋੜੋ
+
+
+ ਕਸਟਮ URL ਜੋੜੋ
+
+
+ ਆਪਣੇ-ਆਪ ਪੂਰਾ ਕਰਨ ਲਿੰਕ ਜੋੜੋ
+
+
+ ਕੂਕੀਜ਼ ਅਤੇ ਸਾਈਟ ਡਾਟਾ
+
+
+ ਡਾਟਾ ਚੋਣਾਂ
+
+
+ ਕਸਟਮ URL ਹਟਾਓ
+
+
+ ਹੋਰ ਸਿੱਖੋ
+
+
+ ਕਸਟਮ ਸਵੈਪੂਰਾ URL ਜੋੜੋ ਅਤੇ ਪ੍ਰਬੰਧਿਤ ਕਰੋ।
+
+
+ ਜੋੜਨ ਲਈ URL
+
+
+ URL ਪੇਸਟ ਕਰੋ ਜਾਂ ਦਾਖਲ ਕਰੋ
+
+
+ ਉਦਾਹਰਨ: mozilla.org
+
+
+ ਉਦਾਹਰਨ: example.com
+
+
+ ਨਵਾਂ ਕਸਟਮ URL ਜੋੜਿਆ ਗਿਆ।
+
+
+ ਹਟਾਓ
+
+
+ ਹਟਾਓ
+
+
+ ਤੁਹਾਡੇ ਦੁਆਰਾ ਦਾਖਲ ਕੀਤੇ ਗਏ URL ਨੂੰ ਡਬਲ-ਚੈਕ ਕਰੋ।
+
+ ਭਾਸ਼ਾ
+
+ ਸਿਸਟਮ ਮੂਲ
+
+ ਪਰਦੇਦਾਰੀ
+ ਇਸ਼ਤਿਹਾਰ ਟਰੈਕਰਾਂ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+ ਕੁਝ ਇਸ਼ਤਿਹਾਰ ਸਾਈਟ ਦੇ ਦੌਰੇ ਨੂੰ ਟਰੈਕ ਕਰਦੇ ਹਨ, ਭਾਵੇਂ ਤੁਸੀਂ ਇਸ਼ਤਿਹਾਰਾਂ ਤੇ ਨਹੀਂ ਕਲਿਕ ਕਰਦੇ
+ ਵਿਸ਼ਲੇਸ਼ਣ ਟਰੈਕਰਾਂ ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+ ਟੇਪਿੰਗ ਅਤੇ ਸਕ੍ਰੌਲਿੰਗ ਵਰਗੀਆਂ ਗਤੀਵਿਧੀਆਂ ਇਕੱਤਰ ਕਰਨ, ਵਿਸ਼ਲੇਸ਼ਣ ਕਰਨ ਅਤੇ ਮਾਪਣ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ
+ ਸਮਾਜਿਕ ਟਰੈਕਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ
+ ਸਾਈਟਾਂ ਉੱਤੇ ਤੁਹਾਡੇ ਵਲੋਂ ਖੋਲ੍ਹਣ ਨੂੰ ਟਰੈਕ ਕਰਨ ਅਤੇ ਸਾਂਝਾ ਕਰਨ ਵਾਲੇ ਬਟਨਾਂ ਵਾਗੂੰ ਕੰਮ ਕਰਨ ਲਈ ਜੋੜਿਆ ਗਿਆ
+ ਹੋਰ ਸਮੱਗਰੀ ਟਰੈਕਰਾਂ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+ ਸਮਰੱਥ ਬਣਾਉਣ ਨਾਲ ਕੁਝ ਪੰਨੇ ਅਚਾਨਕ ਵਿਹਾਰ ਕਰ ਸਕਦੇ ਹਨ
+ ਕੂਕੀਜ਼ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+
+ ਨਹੀਂ, ਧੰਨਵਾਦ
+ ਸਿਰਫ਼ 3ਜੀ ਧਿਰ ਦੇ ਟਰੈਕਰ ਕੂਕੀਜ਼ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+ ਸਿਰਫ 3ਜੀ ਪਾਰਟੀ ਕੂਕੀਜ਼ ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+ ਅੰਤਰ-ਸਾਈਟ ਕੂਕੀਜ਼ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ
+ ਹਾਂ ਜੀ
+
+
+ ਐਪ ਅਣ-ਲਾਕ ਕਰਨ ਲਈ ਫਿੰਗਰਪਰਿੰਟ ਵਰਤੋ
+
+
+ ਜੇ ਤੁਸੀਂ ਸ਼ਾਰਟਕੱਟ ਜੋੜਿਆ ਹੈ ਜਾਂ ਜਦੋਂ ਵੈੱਬਸਾਈਟ ਪਹਿਲਾਂ ਹੀ %s ਵਿੱਚ ਖੁੱਲ੍ਹੀ ਹੈ ਤਾਂ ਫਿੰਗਰਪਰਿੰਟ ਵਰਤ ਕੇ ਅਣ-ਲਾਕ ਕਰੋ।
+
+
+ ਭੇਤੀ
+
+ ਐਪਾਂ ਸਵਿੱਚ ਕਰਨ ਦੌਰਾਨ ਵੈੱਬ ਸਫ਼ੇ ਲੁਕਾਓ ਅਤੇ ਸਕਰੀਨਸ਼ਾਟ ਲੈਣ ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ।
+
+ ਸੁਰੱਖਿਆ
+
+ ਕਾਰਗੁਜ਼ਾਰੀ
+ ਵੈੱਬ ਫ਼ੋਂਟ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+ ਨਤੀਜੇ ਵਜੋਂ ਆਈਕਾਨ ਜਾਂ ਚਿੱਤਰ ਲਾਪਤਾ ਹੋ ਸਕਦੇ ਹਨ
+
+ JavaScript \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+ ਸਫ਼ੇ ਤੇਜ਼ੀ ਨਾਲ ਲੋਡ ਹੋ ਸਕਦੇ ਹਨ, ਪਰ ਅਚਾਨਕ ਹੀ ਵਿਵਹਾਰ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ
+
+
+ %1$s ਨੂੰ ਮੂਲ ਬਰਾਊਜ਼ਰ ਬਣਾਓ
+
+ Mozilla
+ ਵਰਤੋਂ ਸਬੰਧੀ ਡੇਟਾ ਭੇਜੋ
+
+
+ ਹੋਰ ਜਾਣੋ
+
+
+ ਮੌਜ਼ੀਲਾ ਭਰਪੂਰ ਕੋਸ਼ਿਸ਼ ਕਰਦਾ ਹੈ ਕਿ ਸਿਰਫ ਉਹੀ ਕੁੱਝ ਇਕੱਠਾ ਕੀਤਾ ਜਾਵੇ ਜੋ ਕਿ %1$s ਨੂੰ ਹਰੇਕ ਲਈ ਮੁਹੱਈਆ ਕਰਾਉਣ ਅਤੇ ਹੋਰ ਸੁਧਾਰਨ ਲਈ ਲੋੜੀਂਦਾ ਹੋਵੇ।
+
+
+ ਪਰਦੇਦਾਰੀ ਸੂਚਨਾ
+
+
+ ਲਸੰਸ ਬਾਰੇ ਜਾਣਕਾਰੀ
+
+
+ ਸਾਡੇ ਵਲੋਂ ਵਰਤੀਆਂ ਲਾਇਬਰੇਰੀਆਂ
+
+
+ %s | OSS ਲਾਇਬਰੇਰੀਆਂ
+
+
+ %1$s ਦੇ ਬਾਰੇ
+
+
+ ਇੰਸਟਾਲ ਕੀਤੇ ਖੋਜ ਇੰਜਣ
+
+
+ ਖੋਜ ਇੰਜਣ ਚੁਣੋ
+
+
+ ਮੂਲ ਖੋਜ ਇੰਜਣ ਨੂੰ ਬਹਾਲ ਕਰੋ
+
+
+ + ਹੋਰ ਖੋਜ ਇੰਜਣ ਨੂੰ ਜੋੜੋ
+ ਖੋਜ ਇੰਜਣਾਂ ਨੂੰ ਹਟਾਓ
+ ਹਟਾਓ
+
+
+ ਹੋਰ ਖੋਜ ਇੰਜਣ ਨੂੰ ਜੋੜੋ
+
+ ਤੁਹਾਡਾ ਪਸੰਦੀਦਾ ਇੰਜਣ ਚੁਣੋ:
+
+
+ ਖੋਜ ਇੰਜਣ ਨੂੰ ਜੋੜੋ
+
+ ਖੋਜ ਇੰਜਣ ਦਾ ਨਾਂ
+ ਵਰਤਣ ਲਈ ਖੋਜ ਸਤਰ
+ ਸੰਭਾਲੋ
+
+
+ ਜਿਵੇਂ: example.com/search/?q=%s
+
+ ਨਵਾਂ ਖੋਜ ਇੰਜਣ ਜੋੜਿਆ ਗਿਆ।
+
+ ਖੋਜ ਇੰਜਣ ਨਾਂ ਦਿਓ
+ ਇੱਕ ਇੰਸਟਾਲ ਕੀਤੇ ਖੋਜ ਇੰਜਣ ਪਹਿਲਾਂ ਹੀ ਉਹ ਨਾਂ ਵਰਤ ਰਿਹਾ ਹੈ।
+
+ ਖੋਜ ਸਤਰ ਦਿਓ
+
+ ਜਾਂਚ ਕਰੋ ਕਿ ਖੋਜ ਲਾਈਨ ਮਿਲਦੀ ਹੈ ਉਦਾਹਰਨ ਫਾਰਮੈਟ
+
+
+ ਇਨਪੁਟ ਨੂੰ ਹਟਾਓ
+
+
+ ਰੱਦ ਕਰੋ
+
+
+ ਬਰਾਊਜ਼ ਕਰਨ ਦਾ ਅਤੀਤ ਮਿਟਾਓ
+
+
+ ਟੈਬ ਖੋਲੋ: %1$s
+
+
+ ਸੁਰੱਖਿਅਤ ਕਨੈਕਸ਼ਨ
+
+
+ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ
+
+
+ ਵੈੱਬਸਾਈਟ ਲੋਡ ਕੀਤੀ ਗਈ
+
+
+ ਹੋਰ ਚੋਣਾਂ
+
+
+ ਹੋਰ ਚੋਣਾਂ ਬਟਨ
+
+
+ ਅੱਗੇ ਵੱਲ ਨੈਵੀਗੇਟ ਕਰੋ
+
+
+ ਵੈੱਬਸਾਈਟ ਨੂੰ ਰੀਲੋਡ ਕਰੋ
+
+
+ ਪਿੱਛੇ ਵੱਲ ਨੈਵੀਗੇਟ ਕਰੋ
+
+
+ ਵੈੱਬਸਾਈਟ ਨੂੰ ਲੋਡ ਕਰਨਾ ਬੰਦ ਕਰੋ
+
+
+ ਪਿਛਲੇ ਐਪ \'ਤੇ ਜਾਓ
+
+
+ ਪਾਬੰਦੀ ਲਾਏ ਟਰੈਕਾਂ ਦੀ ਗਿਣਤੀ
+
+
+ ਟਰੈਕਰਾਂ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+ ਤੁਹਾਡੇ ਹੱਕ
+
+ ਲਿੰਕ ਨੂੰ ਹੋਰ ਟੈਬ ਵਿੱਚ ਖੋਲ੍ਹੋ
+
+ ਤੁਸੀਂ %2$s ਵਿੱਚ ਇਹ ਲਿੰਕ ਖੋਲ੍ਹਣ ਲਈ %1$s ਨੂੰ ਛੱਡ ਸਕਦੇ ਹੋ।
+
+ ਅਜਿਹੀ ਐਪ ਲੱਭੋ ਜੋ ਲਿੰਕ ਨੂੰ ਖੋਲ੍ਹ ਸਕੇ
+
+ ਤੁਹਾਡੇ ਯੰਤਰ ਉੱਪਰਲੀ ਕੋਈ ਵੀ ਐਪ ਇਸ ਤੰਦ ਨੂੰ ਖੋਲ੍ਹਣ ਦੇ ਕਾਬਲ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ %2$s ਦੇ ਲਈ ਕਿਸੇ ਕਾਬਲ ਐਪ ਨੂੰ ਲੱਭਣ ਲਈ %1$s ਛੱਡ ਸਕਦੇ ਹੋ।
+
+ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ ਤੋਂ ਬਾਹਰ ਨਿਕਲਣਾ ਹੈ?
+
+
+ %1$s ਮੁਕੰਮਲ
+
+
+ ਖੋਲ੍ਹੋ
+
+
+
+
+
+
+
+
+
+ ਸ਼ਾਰਟਕੱਟਾਂ ਵਿੱਚ ਜੋੜਿਆ!
+
+ ਸਰਵਰ ਨਹੀਂ ਲੱਭਿਆ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ਬੰਦ ਕਰੋ
+
+
+
+ %1$s ਵਲੋਂ ਜੀ ਆਇਆਂ ਨੂੰ
+
+
+ ਤੇਜ਼। ਨਿੱਜੀ। ਬਿਨਾਂ ਕਿਸੇ ਰੋਕ ਟੋਕ ਦੇ।
+
+
+ ਸ਼ੁਰੂ ਕਰੀਏ
+
+
+
+ %1$s ਹੋਰਨਾਂ ਬਰਾਊਜ਼ਰਾਂ ਵਰਗਾ ਨਹੀਂ ਹੈ
+
+
+ ਵਧੇਰੇ ਪਰਦੇਦਾਰੀ ਵਾਸਤੇ ਜਦੋਂ ਵੀ ਤੁਸੀਂ ਐਪ ਨੂੰ ਬੰਦ ਕਰਦੇ ਹੋ ਤਾਂ ਅਸੀਂ ਤੁਹਾਡੇ ਅਤੀਤ ਨੂੰ ਮਿਟਾ ਦਿੰਦੇ ਹਾਂ।
+
+
+
+ ਤੁਹਾਡੇ ਵਲੋਂ ਹਰ ਲਿੰਕ ਨੂੰ ਖੋਲ੍ਹੇ ਡਾਟੇ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ %1$s ਨੂੰ ਆਪਣਾ ਮੂਲ ਬਰਾਊਜ਼ਰ ਬਣਾਓ।
+
+
+ ਮੂਲ ਬਰਾਊਜ਼ਰ ਬਣਾਓ
+
+
+ ਛੱਡੋ
+
+
+
+ ਆਪਣੀ ਪਰਦੇਦਾਰੀ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ
+
+ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ ਨੂੰ ਅਗਲੇ ਪੱਧਰ ਤੇ ਲੈ ਜਾਓ। ਇਸ਼ਤਿਹਾਰ ਅਤੇ ਹੋਰ ਸਮੱਗਰੀ, ਜੋ ਕਿ ਤੁਹਾਨੂੰ ਸਾਈਟਾਂ ਉੱਤੇ ਟਰੈਕ ਕਰ ਅਤੇ ਸਫ਼ਾ ਲੋਡ ਕਰਨ ਨੂੰ ਹੌਲੀ ਕਰ ਸਕਦੀ ਹੈ, ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ।
+
+
+ ਤੁਹਾਡੀ ਖੋਜ, ਤੁਹਾਡਾ ਢੰਗ
+
+ ਕੁਝ ਵੱਖਰਾ ਲੱਭ ਰਹੇ ਹੋ? ਸੈਟਿੰਗਾਂ ਵਿੱਚੋ ਹੋਰ ਮੂਲ ਖੋਜ ਇੰਜਣ ਨੂੰ ਚੁਣੋ।
+
+
+ ਆਪਣੀ ਮੁੱਖ ਸਕਰੀਨ ਲਈ ਸ਼ਾਰਟਕਟ ਜੋੜੋ
+
+
+ %1$s ਵਿੱਚ ਫ਼ੌਰਨ ਆਪਣੀਆਂ ਪਸੰਦ ਦੀਆਂ ਸਾਈਟਾਂ ਉੱਤੇ ਜਾਓ। ਬੱਸ %1$s ਮੇਨੂ ਵਿੱਚ \"ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਜੋੜੋ\" ਨੂੰ ਚੁਣੋ।
+
+
+ ਪਰਦੇਦਾਰੀ ਨੂੰ ਆਦਤ ਬਣਾਓ
+
+
+ %1$s ਨੂੰ ਆਪਣਾ ਮੂਲ ਬਰਾਊਜ਼ਰ ਬਣਾਓ ਅਤੇ ਜਦੋਂ ਵੀ ਤੁਸੀਂ ਹੋਰ ਐਪਾਂ ਤੋਂ ਵੈਬ-ਸਫ਼ੇ ਨੂੰ ਖੋਲ੍ਹੋ ਤਾਂ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ ਦਾ ਫਾਇਦਾ ਲਵੋ।
+
+ ਠੀਕ ਹੈ, ਸਮਝ ਗਏ!
+ ਛੱਡੋ
+ ਅੱਗੇ
+
+
+ -
+
+
+ ਜੋੜੋ
+
+
+ ਹਾਂ
+
+
+ ਰੱਦ ਕਰੋ
+
+
+ ਨਹੀਂ
+
+
+ ਸ਼ਾਰਟਕੱਟ ਵਾਧਾ ਕੀਤੀ ਟਰੈਕਿੰਗ ਸੁਰੱਖਿਆ ਨੂੰ ਅਸਮਰੱਥ ਕਰਕੇ ਖੁੱਲ੍ਹੇਗਾ
+
+
+ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ ਸ਼ੈਸ਼ਨ
+
+
+ ਸੂਚਨਾਵਾਂ ਛੂਹਣ ਨਾਲ ਤੁਹਾਡੇ %1$s ਸ਼ੈਸ਼ਨ ਨੂੰ ਮਿਟਾਉਣ ਦੇਣਗੀਆਂ। ਤੁਹਾਨੂੰ ਐਪ ਖੋਲ੍ਹਣ ਜਾਂ ਆਪਣੇ ਬਰਾਊਜ਼ਰ ਵਿੱਚ ਚੱਲਣਾ ਵੇਖਣ ਦੀ ਲਓੜ ਨਹੀਂ ਹੈ।
+
+
+ ਬਰਾਊਜ਼ ਕਰਨ ਦਾ ਅਤੀਤ ਮਿਟਾਓ
+
+
+ Firefox ਡਾਊਨਲੋਡ ਕਰੋ
+
+
+
+
+
+ Mozilla ਜਨਤਕ ਲਸੰਸ ਅਤੇ ਹੋਰ ਖੁੱਲ੍ਹੇ ਵਸੀਲਿਆਂ ਦੇ ਲਸੰਸਾਂ ਦੀਆਂ ਸ਼ਰਤਾਂ ਹੇਠ ਮੁਹੱਈਆ ਕਰਵਾਇਆ ਗਿਆ ਹੈ।]]>
+
+
+ ਇੱਥੇਮਿਲ ਸਕਦੀ ਹੈ।]]>
+
+
+ ਲਸੰਸਾਂ ਹੇਠ ਮਿਲਦਾ ਹੈ।]]>
+
+
+ GNU ਜਰਨਲ ਪਬਲਿਕ ਲਸੰਸ v3 ਦੇ ਤਹਿਤ ਕਰਦਾ ਹੈ ਅਤੇ ਇਹ ਇੱਥੇ ਮੌਜੂਦ ਹੈ ।]]>
+
+
+ ਵਰਤੋਂਕਾਰ-ਨਾਂ
+ ਪਾਸਵਰਡ
+ ਸਾਫ ਕਰੋ
+
+
+
+ ਸੁਰੱਖਿਅਤ ਕਨੈਕਸ਼ਨ
+ ਅਸੁਰੱਖਿਅਤ ਕਨੈਕਸ਼ਨ
+
+ ਜਾਂਚਿਆ: %1$s
+
+
+ ਸਾਈਟ ਦੀ ਸੁਰੱਖਿਆ
+ URL ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ
+
+
+ ਸਫ਼ੇ ਵਿੱਚ ਲੱਭੋ
+
+
+ ਸਫ਼ੇ ਵਿੱਚ ਲੱਭੋ
+
+
+ %1$d/%2$d
+
+ %2$d ਵਿੱਚੋ %1$d ਬਾਹਰ
+
+
+ ਅਗਲਾ ਨਤੀਜਾ ਲੱਭੋ
+
+ ਪਿਛਲਾ ਨਤੀਜਾ ਲੱਭੋ
+
+ ਸਫ਼ੇ ਵਿੱਚ ਲੱਭਣ ਨੂੰ ਖਾਰਜ ਕਰੋ
+
+
+
+
+ ਡੈਸਕਟਾਪ ਸਾਈਟ ਦੀ ਮੰਗ
+
+
+ ਡੈਸਕਟਾਪ ਸਾਈਟ
+
+
+ URL ਕਾਪੀ ਕੀਤਾ
+
+
+ ਡਿਵੈਲਪਰ ਟੂਲ
+
+
+ ਲਿੰਕ ਐਪਾਂ ਵਿੱਚ ਖੋਲ੍ਹੋ
+
+
+ ਤਕਨੀਕੀ
+
+
+ ਸਾਈਟ ਇਜਾਜ਼ਤਾਂ
+
+
+ ਕੂਕੀਜ਼ ਬੈਨਰ ਘਟਾਉਣਾ
+
+
+ ਚਾਲੂ
+
+
+ ਬੰਦ
+
+
+ ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣਾ
+
+
+ ਜਦ ਵੀ ਸੰਭਵ ਹੋਵੇ ਤਾਂ ਕੂਕੀ ਬੇਨਤੀਆਂ ਨੂੰ ਆਪਣੇ-ਆਪ ਖਾਰਜ ਘੱਟ ਬੈਨਰ ਵੇਖੋ।
+
+ -->
+ ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣਾ
+
+
+ ਇਸ ਸਾਈਟ ਲਈ ਚਾਲੂ
+
+
+ ਸਾਈਟ ਇਸ ਵੇਲੇ ਸਹਾਇਕ ਨਹੀਂ ਹੈ
+
+
+ ਇਸ ਸਾਈਟ ਲਈ ਬੰਦ
+
+
+ ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣਾ
+
+
+ ਇਸ ਸਾਈਟ ਲਈ ਬੰਦ
+
+
+ ਇਸ ਸਾਈਟ ਲਈ ਚਾਲੂ
+
+
+ %1$s ਲਈ ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?
+
+
+ %1$s ਲਈ ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣ ਨੂੰ ਬੰਦ ਕਰਨਾ ਹੈ?
+
+
+ %1$s ਇਸ ਸਾਈਟ ਦੀਆਂ ਕੁਕੀਆਂ ਨੂੰ ਸਾਫ਼ ਕਰ ਕੇ ਵਰਕੇ ਨੂੰ ਸੱਜਰਾ ਕਰ ਦੇਵੇਗਾ। ਸਾਰੀਆਂ ਕੁਕੀਆਂ ਨੂੰ ਸਾਫ਼ ਕਰਨ ਨਾਲ ਤੁਸੀਂ ਸਾਈਨ ਆਊਟ ਹੋ ਸਕਦੇ ਹੋ ਜਾਂ ਖਰੀਦਦਾਰੀ ਵਾਲੀ ਟੋਕਰੀ ਖਾਲੀ ਹੋ ਸਕਦੀ ਹੈ।
+
+
+ %1$s ਕੂਕੀ ਬੇਨਤੀਆਂ ਨੂੰ ਆਪਣੇ-ਆਪ ਹੀ ਰੱਦ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ ਕਰ ਸਕਦਾ ਹੈ।
+
+
+ ਇਹ ਸਾਈਟ ਇਸ ਵੇਲੇ ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣ ਲਈ ਸਹਾਇਕ ਨਹੀਂ ਹੈ। ਕੀ ਤੁਸੀਂ ਇਸ ਵੈਬਸਾਈਟ ਦੀ ਪੜਤਾਲ ਕਰਨ ਅਤੇ ਭਵਿੱਖ ਵਿੱਚ ਸਹਿਯੋਗ ਜੋੜਨ ਲਈ ਸਾਡੀ ਟੀਮ ਨੂੰ ਬੇਨਤੀ ਕਰਨਾ ਚਾਹੋਗੇ?
+
+
+ ਰੱਦ ਕਰੋ
+
+
+ ਸਹਿਯੋਗ ਲਈ ਬੇਨਤੀ
+
+
+ ਸਹਿਯੋਗ ਸਾਈਟ ਲਈ ਬੇਨਤੀ ਕੀਤੀ ਗਈ ਹੈ।
+
+
+ ਸਹਿਯੋਗ ਸਾਈਟ ਲਈ ਬੇਨਤੀ ਕੀਤੀ ਗਈ ਹੈ।
+
+
+
+ %1$s ਨੇ ਖਿਝਾਊ ਕੂਕੀ ਬੈਨਰ ਖਾਰਜ ਕਰਨ ਲਈ ਕੂਕੀ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ ਕੀਤੀ ਹੈ।\n\%2$s ਵਿੱਚੋਂ ਕੂਕੀ ਬੈਨਰ ਪਸੰਦਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ।
+
+
+ ਸੈਟਿੰਗਾਂ
+
+
+ ਆਪੇ-ਚਲਾਓ
+
+
+ ਇਸਨੂੰ ਸਹਿਮਤੀ ਦੇਣ ਲਈ:
+
+
+ 1. Android ਸੈਟਿੰਗਾਂ ਉੱਤੇ ਜਾਓ
+
+
+ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਛੂਹੋ]]>
+
+
+ ਸੈਟਿੰਗਾਂ ਉੱਤੇ ਜਾਓ
+
+
+ %1$s ਨੂੰ ਚਾਲੂ ਲਈ ਬਦਲੋ]]>
+
+
+ ਕੈਮਰਾ
+
+
+ ਮਾਈਕਰੋਫ਼ੋਨ
+
+
+ ਟਿਕਾਣਾ
+
+
+ ਸੂਚਨਾਵਾਂ
+
+
+ DRM-ਕੰਟਰੋਲ ਕੀਤੀ ਸਮੱਗਰੀ
+
+
+ ਮਨਜ਼ੂਰੀ ਲਈ ਪੁੱਛੋ
+
+
+ ਪਾਬੰਦੀ ਲਗਾਈ
+
+
+ ਇਜਾਜ਼ਤ ਦਿੱਤੇ
+
+
+ Android ਵਲੋਂ ਪਾਬੰਦੀ ਲਗਾਈ
+
+
+ ਆਡੀਓ ਅਤੇ ਵੀਡਿਓ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ
+
+
+ ਸਿਰਫ਼ ਆਡੀਓ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ
+
+
+ ਸਿਫ਼ਾਰਸ਼ੀ
+
+
+ ਆਡੀਓ ਅਤੇ ਵੀਡਿਓ ਤੇ ਪਾਬੰਦੀ ਲਾਓ
+
+
+ ਅਧਿਐਨ
+
+
+ Firefox ਸਮੇਂ ਸਮੇਂ ਉੱਤੇ ਅਧਿਐਨ ਇੰਸਟਾਲ ਕਰ ਤੇ ਚਲਾ ਸਕਦਾ ਹੈ।
+
+
+ ਹੋਰ ਜਾਣੋ
+
+
+ ਤਬਦੀਲੀਆਂ ਲਾਗੂ ਕਰਨ ਲਈ ਐਪਲੀਕੇਸ਼ਨ ਬੰਦ ਹੋਵੇਗੀ
+
+
+ ਹਟਾਓ
+
+
+ ਸਰਗਰਮ
+
+
+ ਪੂਰਾ ਹੋਇਆ
+
+
+ USB//Wi-Fi ਰਾਹੀਂ ਰਿਮੋਟ ਡੀਬੱਗ ਕਰਨਾ
+
+
+ ਅਣ-ਲਾਕ ਕਰੋ
+
+
+ ਆਪਣੇ ਫਿੰਗਰਪਰਿੰਟ ਵਰਤ ਕੇ ਤਸਦੀਕ ਕਰੋ
+
+
+ ਹੁਣ ਤੁਸੀ ਆਪਣੇ ਮੌਜੂਦਾ ਐਪ ਸ਼ੈਸ਼ਨ ਲਈ ਆਪਣੇ ਫਿੰਗਰਪਰਿੰਟ ਨੂੰ ਵਰਤ ਸਕਦੇ ਹੋ।
+
+
+ ਲਿੰਕ ਨਵੇੰ ਸ਼ੈਸ਼ਨ ਚ ਖੋਲ੍ਹੋ
+
+
+ ਫਿੰਗਰਪਰਿੰਟ ਆਈਕਾਨ
+
+
+ ਫਿੰਗਰਪਰਿੰਟ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ। ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ।
+
+
+ ਉਂਗਲ ਬਹੁਤ ਤੇਜ਼ੀ ਨਾਲ ਹਿਲਾਈ ਗਈ। ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ।
+
+
+ ਖੋਜ ਦੇ ਸੁਝਾਆਵਾਂ ਨੂੰ ਵੇਖਣਾ ਹੈ?
+
+
+ ਸੁਝਾਅ ਲੈਣ ਲਈ %1$s ਨੂੰ ਤੁਹਾਡੇ ਵਲੋਂ ਲਿਖੀ ਗਈ ਜਾਣਕਾਰੀ ਖੋਜ ਇੰਜਣ ਨੂੰ ਭੇਜਣ ਦੀ ਲੋੜ ਹੈ।
+
+
+ ਨਹੀਂ
+
+
+ ਹਾਂ
+
+
+ ਕੁਝ ਖੋਜ ਇੰਜਣ ਸੁਝਾਅ ਨਹੀਂ ਵੇਖਾ ਸਕਦੇ ਹਨ।
+
+
+ ਰੱਦ ਕਰੋ
+
+
+
+
+ ਸਾਈਟ ਅਜੀਬ ਵਿਹਾਰ ਕਰ ਰਹੀ ਹੈ?\n ਟਰੈਕਿੰਗ ਸੁਰੱਖਿਆ ਨੂੰ ਬੰਦ ਕਰਕੇ ਕੋਸ਼ਿਸ਼ ਕਰੋ
+
+
+ ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਜੋੜੋ]]>
+
+
+ ਹਰ ਲਿੰਕ ਨੂੰ %1$s ਵਿੱਚ ਖੋਲ੍ਹੋ\n %1$s ਨੂੰ ਮੂਲ ਬਰਾਊਜ਼ਰ ਬਣਾਓ
+
+
+ ਤੁਹਾਡੇ ਵਲੋਂ ਅਕਸਰ ਸਾਈਟਾਂ ਲਈ ਆਪੇ ਪੂਰਨ URLs\n
+ਸਿਰਨਾਵਾਂ ਪੱਟੀ ਵਿੱਚ ਕਿਸੇ ਵੀ URL ਨੂੰ ਲੰਮਾ ਸਮਾਂ ਦਬਾ ਕੇ ਰੱਖੋ
+
+
+
+ ਲਿੰਕ ਨੂੰ ਨਵੀਂ ਟੈਬ ਵਿੱਚ ਖੋਲ੍ਹੋ\n
+ ਸਫ਼ੇ ਉੱਤੇ ਕਿਸੇ ਵੀ ਲਿੰਕ ਨੂੰ ਲੰਮਾ ਸਮਾਂ ਦਬਾ ਕੇ ਰੱਖੋ
+
+
+
+ ਸ਼ੁਰੂਆਤੀ ਸਕਰੀਨ ਉੱਤੇ ਗੁਰ ਬੰਦ ਕਰੋ
+
+
+ ਨਵੀਂ ਟੈਬ ਖੋਲ੍ਹੀ ਗਈ
+
+
+ ਬਦਲੋ
+
+
+ ਪੂਰੀ ਸਕਰੀਨ ਢੰਗ ਵਿੱਚ ਜਾ ਰਿਹਾ ਹੈ
+
+
+ ਲਿੰਕ ਨੂੰ ਤੁਰੰਤ ਨੂੰ ਨਵੀਂ ਟੈਬ ਵਿੱਚ ਬਦਲੋ
+
+
+ ਸੰਭਾਵਿਤ ਖ਼ਤਰਨਾਕ ਅਤੇ ਭਰਮਪੂਰਕ ਸਾਈਟਾਂ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+ ਰਿਪੋਰਟ ਕੀਤੀਆਂ ਭਰਮਪੂਰਕ ਤੇ ਹਮਲਾਪੂਰਕ ਸਾਈਟਾਂ, ਮਾਲਵੇਅਰ ਸਾਈਟਾਂ ਅਤੇ ਬੇਲੋੜ ਸਾਫਟਵੇਅਰ ਵਾਲੀਆਂ ਸਾਈਟਾਂ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ।
+
+
+ ਸਿਰਫ਼-HTTPS ਢੰਗ
+
+
+ ਵਾਧਾ ਕੀਤੀ ਸੁਰੱਖਿਆ ਲਈ HTTPS ਇੰਕ੍ਰਿਪਸ਼ਨ ਪਰੋਟੋਕਾਲ ਵਰਤ ਕੇ ਸਾਈਟਾਂ ਨਾਲ ਕਨੈਕਟ ਕਰਨ ਦੀ ਆਪਣੇ-ਆਪ ਕੋਸ਼ਿਸ਼ ਕਰੋ।
+
+
+ ਛੋਟਾਂ
+
+ ਤੁਸੀਂ ਇਹਨਾਂ ਵੈੱਬਸਾਈਟਾਂ ਲਈ ਸਮੱਗਰੀ ਪਾਬੰਦੀ ਨੂੰ ਅਸਮਰੱਥ ਕਰ ਚੁੱਕੇ ਹੋ।
+
+ ਹਟਾਓ
+
+ ਸਾਰੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਹਟਾਓ
+
+
+ ਕੂਕੀਜ਼ \'ਤੇ ਪਾਬੰਦੀ ਲਗਾਓ
+
+
+ ਕੀ ਤੁਸੀਂ ਕੂਕੀਜ਼ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?
+
+
+ ਟੈਬ ਨਸ਼ਟ ਹੋਈ
+
+ ਅਫ਼ਸੋਸ। ਸਾਨੂੰ ਇਸ ਟੈਬ ਨਾਲ ਸਮੱਸਿਆ ਆ ਰਹੀ ਹੈ।
+
+ ਨਿੱਜੀ ਬਰਾਊਜ਼ਰ ਵਜੋਂ, ਅਸੀਂ ਇਸ ਟੈਬ ਲਈ ਕੁਝ ਨਹੀਂ ਸੰਭਾਲਿਆ ਅਤੇ ਬਹਾਲ ਨਹੀਂ ਕਰ ਸਕਦੇ ਹਾਂ।
+
+ ਟੈਬ ਬੰਦ ਕਰੋ
+
+
+
+
+
+ ਨਸ਼ਟ ਹੋਣ ਰਿਪੋਰਟਾਂ ਮੋਜ਼ੀਲਾ ਨੂੰ ਭੇਜੋ
+
+
+
+
+ %s ਤੋਂ ਪਾਬੰਦੀ ਲਾਏ ਗਏ ਟਰੈਕਰ
+
+ ਸਮੱਗਰੀ
+
+ ਇਸ਼ਤਿਹਾਰਬਾਜੀ
+
+ ਸਮਾਜਿਕ
+
+ ਵਿਸ਼ਲੇਸ਼ਣ
+
+ ਵਾਧਾ ਕੀਤੀ ਟਰੈਕਿੰਗ ਸੁਰੱਖਿਆ
+
+ ਇਸ ਸਾਈਟ ਲਈ ਸੁਰੱਖਿਆ ਬੰਦ ਹੈ
+
+ ਇਸ ਸਾਈਟ ਲਈ ਸੁਰੱਖਿਆ ਚਾਲੂ ਹੈ
+
+ ਕਨੈਕਸ਼ਨ ਸੁਰੱਖਿਅਤ ਹੈ
+
+ ਕਨੈਕਸ਼ਨ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਹੈ
+
+ ਪਾਬੰਦੀ ਲਾਉਣ ਲਈ ਟਰੈਕਰ ਤੇ ਸਕ੍ਰਿਪਟਾਂ
+
+
+ ਪਿੱਛੇ ਜਾਓ
+
+
+
+ ਹਟਾਓ
+
+
+ ਨਾਂ-ਬਦਲੋ
+
+ ਨਾਂ ਬਦਲੋ
+
+
+ ਸ਼ਾਰਟਕੱਟ ਨਾਂ
+
+
+ ਜਦੋਂ ਤੁਸੀਂ %1$s ਅਤੀਤ ਮਿਟਾਉਂਦੇ ਹੋ ਤਾਂ ਸੰਭਾਲੇ ਅਤੇ ਸਾਂਝੇ ਕੀਤੇ ਚਿੱਤਰ <b>ਹਟਾ ਦਿੱਤੇ ਜਾਣਗੇ</b>
+
+
+
+ ਥੀਮ
+
+ ਹਲਕਾ
+
+ ਗੂੜ੍ਹਾ
+
+ ਬੈਟਰੀ ਬੱਚਤਕਾਰ ਵਲੋਂ ਨਿਯਤ
+
+ ਡਿਵਾਈਸ ਥੀਮ ਦੇ ਮੁਤਾਬਕ
+
+
+
+ ਇਹ ਸਾਈਟ HTTPS ਲਈ ਸਹਾਇਕ ਨਹੀਂ ਹੈ
+
+
+ ਹੋਰ ਜਾਣੋ
+ ਇਸ ਸੈਟਿੰਗ ਨੂੰ ਸੈਟਿੰਗਾਂ > ਪਰਦੇਦਾਰੀ ਤੇ ਸੁਰੱਖਿਆ > ਸੁਰੱਖਿਆ ਵਿੱਚੋਂ ਬਦਲੋ।]]>
+
+
+ ਕਨੈਕਸ਼ਨ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਹੈ
+
+
+
+ ਜੇ ਤੁਸੀਂ ਇਸ ਸਰਵਰ ਨਾਲ ਪਹਿਲਾਂ ਕਾਮਯਾਬੀ ਨਾਲ ਕਨੈਕਟ ਹੋ ਚੁੱਕੇ ਹੋ ਤਾਂ ਇਹ ਆਰਜ਼ੀ ਗਲਤੀ ਹੋ ਸਕਦੀ ਹੈ।
+ ]]>
+
+
+ ਕੋਈ ਸਾਈਟ ਦੀ ਨਕਲ ਕਰਦਾ ਹੋ ਸਕਦਾ ਹੈ ਅਤੇ ਜਾਰੀ ਰੱਖਣਾ ਖ਼ਤਰਨਾਕ ਹੋ ਸਕਦਾ ਹੈ।
+
+ %1$s %2$s ਉੱਤੇ ਭਰੋਸਾ ਨਹੀਂ ਕਰਦਾ ਹੈ, ਕਿਉਂਕਿ ਇਹ ਸਰਟੀਫਿਕੇਟ ਨੂੰ ਜਾਰੀ ਕਰਨ ਵਾਲਾ ਅਣਪਛਾਤਾ ਹੈ, ਸਰਟੀਫਿਕੇਟ ਖੁਦ-ਦਸਤਖਤੀ ਹੈ ਜਾਂ ਸਰਵਰ ਠੀਕ ਵਿਚਕਾਰਲੇ ਸਰਟੀਫਿਕੇਟ ਨਹੀਂ ਭੇਜ ਰਿਹਾ ਹੈ।
+ ]]>
+
+
+
+ ਟੈਬ ਬੰਦ ਕਰੋ
+
+
+
+ ਫੜੇ ਗਏ! ਅਸੀਂ ਇਹ ਸਾਈਟ ਨੂੰ ਤੁਹਾਡੀ ਜਾਸੂਸੀ ਕਰਨ ਤੋਂ ਰੋਕਿਆ ਹੈ। ਕਿਸੇ ਵੀ ਵੇੇਲੇ ਢਾਲ ਨੂੰ ਛੂਹ ਕੇ ਵੇਖੋ ਕਿ ਅਸੀਂ ਕਿਸੇ ਉੱਤੇ ਰੋਕ ਲਾਈ ਹੈ।
+
+
+ ਪੋਪ-ਅੱਪ ਬੰਦ ਕਰੋ
+
+
+
+ ਤੁਸੀਂ ਸੁਰੱਖਿਅਤ ਹੋ!
+
+ ਇਹ ਮੂਲ ਸੈਟਿੰਗਾਂ ਮਜ਼ਬੂਤ ਸੁਰੱਖਿਅਤ ਦਿੰਦੀਆਂ ਹਨ। ਪਰ ਤੁਹਾਡੀਆਂ ਖਾਸ ਲੋੜਾਂ ਮੁਤਾਬਕ ਸੈਟਿੰਗਾਂ ਨੂੰ ਢਾਲਣਾ ਸੌਖਾ ਹੈ।
+
+ ਖ਼ਾਰਜ ਕਰੋ
+
+
+ ਸਾਰੇ — ਅਤੀਤ, ਕੂਕੀਜ਼, ਹਰ ਚੀਜ਼ — ਨੂੰ ਰੱਦੀ ਵਿੱਚ ਭੇਜ ਕੇ ਨਵੀਂ ਟੈਬ ਨਾਲ ਸੱਜਰੀ ਸ਼ੁਰੂਆਤ ਲਈ ਇੱਥੇ ਛੂਹੋ।
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ ਬੰਦ ਕਰੋ
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ ਖੋਜ ਵਿਜੈੱਟ
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ਬਰਾਊਜ਼ਿੰਗ ਅਤੀਤ ਮਿਟਾਇਆ ਗਿਆ! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ਆਪਣਾ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ ਸ਼ੈਸ਼ਨ ਸ਼ੁਰੂ ਕਰੋ ਅਤੇ ਅਸੀਂ ਤੁਹਾਡੇ ਵਾਸਤੇ ਟਰੈਕਰਾਂ ਤੇ ਹੋਰ ਬੁਰੀਆਂ ਚੀਜ਼ਾਂ ਉੱਤੇ ਰੋਕ ਲਾਵਾਂਗੇ।
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ਹੁਣ ਤੁਸੀਂ ਆਪਣੀ ਨਿੱਜੀ ਬਰਾਊਜ਼ਿੰਗ ਕਰੋ, ਪਰ ਅਗਲੀ ਵਾਰ ਫ਼ੌਰਨ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਆਪਣੀ ਮੁੱਖ ਸਕਰੀਨ ਤੋਂ %1$s ਵਿਜੈਟ ਵਰਤ ਸਕਦੇ ਹੋ।
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ ਵਿਜੈੱਟ ਨੂੰ ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਜੋੜੋ
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ ਵਿਜੈੱਟ ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਜੋੜਿਆ ਗਿਆ
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-pai/strings.xml b/mobile/android/focus-android/app/src/main/res/values-pai/strings.xml
new file mode 100644
index 0000000000..e4717016d5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-pai/strings.xml
@@ -0,0 +1,354 @@
+
+
+
+
+
+
+
+ Tubspach
+ Jan ik yooch
+
+ Chkwaach
+
+ Tñur paa eemch ŕmee solch
+
+ Byaw´i Sal amchu. Sal m´am. Mtkryee. Mtkweek miyi.
+
+ Chumŕmee m´amu hay payt ŕputub bwiir.
+
+ Chumŕmee m´amu hay payt ŕputub bwiir.
+
+ %1$s chŕmee
+
+ Mkwimu…
+
+ Wawchuha klyeebha paytum jwaknaabchu
+
+ %1$s em msol
+
+ Em msol…
+
+ Jabchu namaan ch hal mchmi
+
+ Chajaanch
+ Sak ntpach
+ Chbtaarch
+ Mir mir mwii
+
+ Chŕmechu ŕ´ap
+
+ Chwichu yak yoya mchubowutemum amchuhal chatewulim chajankyawyu
+
+ Mtbspachmwir amchu ñulyoom mchajanhikyawa
+ Mtubspach amchu ñulyom mchajanhikyawa
+
+ %1$s em ntpachu
+
+ Sam paa m´em
+
+ Sal amchu mujiñ
+
+ Sol
+
+ Ŕmar e sol
+
+ Ŕmar
+
+ Sal amchu mujiñ
+
+
+ Ŕmar e sol
+
+ Ŕmar e sol %1$s
+
+ Jabchu tpaay ŕbokjanchu
+
+ Kabwik chŕmeechu, cookies ee tñurya
+
+ Bu januliwu chiich, yemik maat chulwiyu
+
+ %1$s chŕmechum, nyamch
+
+ Web chapayt wii
+
+ Aplicación jkayum tyak
+
+ Payt
+
+ Tñur kabwik ñum tchech chu chyunk
+
+ Ŕmee
+
+ Kabwik chŕmecuhu mukunabchu
+ %1$s ey ñukwee ñumtñurum bar chŕmechuhal kwiima
+
+ Ñub ik chmiich
+
+ Chŕmeechu
+
+ Chubowu
+
+ Ŕ\'am
+
+ URL yeemik chublwi
+
+ Amchu kajan kajanu
+
+ Wachuha sal chmiich
+
+ Waachu ñumtrcheech
+
+ Waachu numtrcheech
+
+ + URL yeemik wii jam sal chmii
+
+ URL yeemik wii jam sal chmii
+
+ URL yeemik wii jam sal chmii
+
+ Jabchu yemik matchublwiibu
+
+ Cookies e tñur wawchu
+
+ Tñurch t\'t orrum nyaamch
+
+ URLs yemi wiiha ujiiñ
+
+ Terabum ŕpo
+
+ URLs yemi wiha ñumchublwiha ŕmee sal chmii.
+
+ URL ha sal chmii
+
+ URL tublich sal chmii
+
+ Yabwika: mozilla.org
+
+ Yabwika: example.com
+
+ URL jkaay yemi wii sal chmii.
+
+ Ujiñ
+
+ Ujiñ
+
+ URL sal mukjabha mtkweek m´uu.
+
+ Paa kwawchu
+ Sistem kablwiwu ha sal chmii
+
+ Jabchu tpaay
+ Chapayt kwaaw kwimchu ŕmewu mnmakum kjkeetem
+ Chapayt paa chu uubchu ñulyook sal m´amuhal chŕmeewu, may kos myootemkabyuso
+ Chapayt kŕmeechu chkk uu kajanu ha kos mnmakum kjkeetem
+ Ya chapayt myoowu, uukjanuk pay chapayt wichu tawiin nkyuchu
+ Paa waach chkŕmeech paa tketch
+ Wawchul wak sal ñmbaamum m´uu ñwak kabyuk trchech chuha tawinch paa wak ik
+ Chapyt kŕmeech paa t´t kyaata ñwak chapayt jkay ya
+ Ñumchubowum jabchu ñulyook kos janu bootem
+ Cookies tket
+
+ T´am 3 nyumuk cookies uj nyaamch jmuuk jmuukum
+ Cookies jkayha paa t´tkyat
+
+ Ŕalum mchknaa sak muyok mjab hikyawa
+
+ Ŕpoobtem
+ Jabchu web ha tjoot aplicación jkaay chkwa ñwam
+
+ Kryek janu
+
+ Kabyuk trchech chu
+ Tñur web wi tkat
+ Icon ee paa tñurch hal chapayt kamjwilchuhal aaw yome
+
+ JavaScript tket
+ Jabchuhay wayiibrab jukewu yuso, kabyuu imyus kabyuwuha kos ŕpoochtema
+
+ %1$s ha sal amchu jaan myoobka
+
+ Mozilla
+ Kamwiik daatha mwiruha m´emch
+
+ Terabum ŕpo
+
+ Mozilla hay chmwiwuha paytum chyunk ka %1$s paytum wichkyaaw chajaanrab hikyawa.
+
+ Kabwik yemi wii jan yuwuha
+
+ %1$s kabyuwuh
+
+ Motor chŕmechu ñuk chkwachumyok
+
+ Motor chajaana januwa chŕmeech hay jan hikyawa
+
+ + Motor jkaay chŕmeech kyaaw jkaay sal chmii
+ Motor chŕmeechu ujiñ
+ Ujiiñ
+
+ Chŕmeechu sal chmii
+
+ Motor chŕmechu muul
+ Caden ŕmee ñum trchech hi kyawa
+ Chkwaach
+
+ Yabwika: example.com/search/?q=%s
+
+ Motor chŕmeechu jkaay kur sal chmiich.
+
+ Motor sal amchu muul sal chmiich
+ Motor chŕmechu kur sal chmiichum sal yak muul ñu mul.
+
+ Caden chŕmechu ukjabch
+
+ Caden chŕmechu hay tñurchu sal kyakham nyamuha uuch
+
+ Tñurchi tkyeech
+
+ Kos ñul uuten
+
+ Sal amchu mujiñ
+
+ Tñur chiichu sool: %1$s
+
+ Tublich chu jan
+
+ Jukewu
+
+ Web wawu jukeewu
+
+ Chwichu terab
+
+ Tawiinchu chaterab kyaaw
+
+ Jiboom kyaamch
+
+ Web tkweek jukewu
+
+ Sal amchu tkweek jabch
+
+ Web jabchuha chubŕkwich
+
+ Aplicación jubo kyakhal tkweek
+
+ Chŕibchu chapayt ŕmeechuy ŕ´ap kyo
+
+ Chkŕmeech paa tketch
+
+ Mir mir mwii
+
+ Jabchu aplicación jkayk solch
+ %1$s hal mchpaa pay jabchu %2$s msol.
+ Aplicacion ŕit jabchu solum mŕmee
+ Aplicacion dispositiiv mwi ŕitulik yak yoya soltema. %1$s myaam %2$s mŕmee aplicación ŕit kwa solhikyawa.
+ Sal amchu jabch tpaay al chpaa?
+
+ %1$s byir
+
+ Sol
+
+
+
+
+
+
+
+
+
+ Paa kubtaar kos chyawtem
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Myeemik chumwiwuha ta mwiluk
+ Sal amchu ksool tpayha myaabraab mwaam. Cehapayt jkay knawchu ee jkay sal yak chumwiwu hal jaab wawchu t´t´orum kyakhal unuu tñur kwimch kajanha chmii chum.
+
+ Myeemik, chmŕmeewu
+ ¿Ñukwee jkaayum yakum mŕmee´e? Chajanchu kur sal chmiich ha chubowu wiiha myok.
+
+ Pantaal kajanha wayiibraw sal mchmii
+ Amchu jnmiraabhal %1$s hal khal koha wamyiibraw sal mtkweek. \"Tñur kwimch namanchuhal mchmii\" im myok %1$s chnamanchuhal.
+
+ Myeemik chmwiwuha myok majanuk
+ %1$s ha sal amchu kajan mik mchmii pay sal amchu myeemik mwii ha kabuwu ha m´uk aplicación o tñur kwimch web hal amchu ha payrum m´uu.
+
+ ¡M´eb´e!
+ Kos ñulm´ebtem
+ Nulkpeeb
+
+ -
+
+ Sal chmii
+
+ Tubspach
+
+ Chyunch sal amchu ŕ´aap
+
+ Chknaabchuhay chynchu ha %1$s ah ŕmara tawinch ŕisum. Kos aplicación ha msol yaŕaaltem o sal amchuhay boo kwawu uch yutem.
+
+ Sal amchu mujiñ
+
+ Firefox munaal
+
+ %1$s software kos codig wiiytem solkyoha Mozilla ha ee jkay kubtar cyobchya.
+
+ M´mulha myoka
+ Ŕ\' amu
+ Tkryee
+
+ Tublich chu jan
+ Tublich chu kos jaan tpaay
+ Say uu: %1$s
+
+ Chŕmeechu myook
+
+ Chŕmeechu myook
+
+ %1$d/%2$d
+
+ Trchechu mk\' kaab
+
+ URL tñurch
+
+ Ñulyomuk tkryeechu USB/Wi-Fi nyaam
+
+
+
+ Waachu klyeeb´e paa wakyaw mŕ´am
+ Amchu chknarp ee paa jwakyaa ŕ ´amcha, amchu malware ee amchu software kos yaŕaalch tpaay mtket.
+
+ Kos ñul´ebchtpaay
+ Chapayt sal kyaak amchu yak yoya mtket.
+ Ujiiñ
+ Amchu ha paytum ujiiñ
+
+ Tñur chmiichu hay kos jantem
+ Tuñur chmiichuhal trchech upaar um unuuch. Ñubyu kwal ibtem.
+ Ñul amchu kŕ´apchuwukyawa kos chapayt chwatem, sakyaaw kos tkweek chajaantem.
+ Tñur chiichu ŕ´aap
+
+
+
+
+ Wuñparum Mozilla kwiim
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-pl/strings.xml b/mobile/android/focus-android/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000000..134bf3888f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,1119 @@
+
+
+
+
+
+
+
+
+ Anuluj
+
+ OK
+
+ Zapisz
+
+
+ Wpisz adres lub szukaj
+
+ Automatyczne przeglądanie prywatne.\nPrzeglądaj. Usuwaj. Powtórz.
+
+
+ Usunięto historię przeglądania.
+ Usunięto historię przeglądania
+
+
+ Usunięto historię przeglądania tej karty.
+
+
+ Wyszukaj „%1$s”
+
+
+ Udostępnij…
+
+
+ Zgłoś problem ze stroną
+
+
+ Otwórz w aplikacji %1$s
+
+
+ Otwórz w…
+
+
+ Dodaj do ekranu głównego
+
+
+ Dodaj do skrótów
+
+ Usuń ze skrótów
+
+
+ Ustawienia
+ O programie
+ Pomoc
+ O prawach użytkownika
+
+
+ Blokowanie
+
+
+ Wyłączenie może rozwiązać niektóre problemy ze stroną
+
+
+ Blokowanie treści
+
+ Wyłączenie może naprawić działanie niektórych stron
+
+
+ Funkcja %1$s
+
+
+ Udostępnij przez
+
+ Usunąć historię przeglądania?
+ Stuknij lub wyczyść to powiadomienie, aby bezpiecznie usunąć historię przeglądania.
+
+
+ Stuknij lub przeciągnij to powiadomienie, aby bezpiecznie usunąć historię przeglądania.
+
+ Usuń historię przeglądania
+
+
+ Otwórz
+
+
+ Usuń i otwórz
+
+
+ Usuń
+
+
+ Usuń historię przeglądania
+
+
+
+ Usuń i otwórz
+
+
+ Usuń i otwórz %1$s
+
+
+
+ Wyszukaj w Focus
+
+ Wyszukaj w Klar
+
+ Wyszukaj w Focus Beta
+
+ Wyszukaj w Focus Nightly
+
+
+ %1$s daje większą kontrolę.
+Jako prywatna przeglądarka umożliwia:
+
+ wyszukiwanie i przeglądanie prosto z aplikacji
+ blokowanie elementów śledzących (można to zmienić w ustawieniach)
+ usuwanie ciasteczek oraz całej historii wyszukiwania i przeglądania
+
+
+%1$s jest tworzony przez Mozillę. Naszym celem jest wspieranie zdrowego i otwartego Internetu.
+Więcej informacji
]]>
+
+
+ Prywatność i bezpieczeństwo
+
+
+ Śledzenie, ciasteczka, udostępniane dane
+
+
+ Domyślna wyszukiwarka, automatyczne uzupełnianie
+
+
+
+
+ O programie %1$s, pomoc
+
+
+ Wzmocniona ochrona przed śledzeniem
+
+
+ Strony
+
+
+ Przełączanie aplikacji
+
+
+ Ogólne
+
+
+ Domyślna przeglądarka, język
+
+
+ Zbieranie i wykorzystywanie danych
+
+ Wyszukiwanie
+
+
+ Podpowiedzi wyszukiwania
+
+ %1$s będzie wysyłał słowa wpisywane na pasku adresu do wyszukiwarki
+
+
+ Domyślna
+
+
+ Wyszukiwarka
+
+
+ Włączone
+
+
+ Wyłączone
+
+
+ Automatyczne uzupełnianie adresów
+
+
+ Dla popularnych stron
+
+
+ Włącz, aby %s automatycznie uzupełniał ponad 450 popularnych adresów na pasku adresu.
+
+
+ Dla dodanych stron
+
+
+ Włącz, aby %s automatycznie uzupełniał Twoje ulubione adresy.
+
+
+ Zarządzaj stronami
+
+
+ Zarządzaj stronami
+
+
+ + Dodaj inny adres
+
+
+ Własna lista automatycznego uzupełniania:
+
+
+ Dodaj adres
+
+
+ Dodaj inny adres
+
+
+ Dodaj inny adres
+
+
+ Dodaj odnośnik do automatycznego uzupełniania
+
+
+ Ciasteczka i dane witryn
+
+
+ Udostępniane dane
+
+
+ Usuń inny adres
+
+
+ Więcej informacji
+
+
+ Dodawanie i zarządzenie innymi adresami automatycznego uzupełniania.
+
+
+ Adres do dodania
+
+
+ Wklej lub wpisz adres
+
+
+ Przykład: mozilla.org
+
+
+ Przykład: example.com
+
+
+ Dodano nowy adres.
+
+
+ Usuń
+
+
+ Usuń
+
+
+ Sprawdź poprawność wpisanego adresu.
+
+ Język
+
+ Domyślny systemu
+
+ Prywatność
+ Blokowanie śledzących reklam
+ Niektóre reklamy (nawet niekliknięte) śledzą odwiedziny zawierających je stron
+ Blokowanie śledzących statystyk
+ Używane do zbierania, analizowania i mierzenia działań użytkownika, takich jak stuknięcia i przewijanie
+ Blokowanie śledzących społecznościowych
+ Osadzane na stronach, aby śledzić ich odwiedziny i dodawać np. przyciski udostępniania
+ Blokowanie pozostałych śledzących
+ Włączenie może spowodować nieoczekiwane zachowanie niektórych stron
+ Blokowanie ciasteczek
+
+
+ Nie, dziękuję
+ Blokowanie tylko śledzących ciasteczek zewnętrznych witryn
+ Blokowanie tylko ciasteczek zewnętrznych witryn
+
+ Blokowanie ciasteczek między witrynami
+ Tak, proszę
+
+
+ Odcisk palca odblokowuje aplikację
+
+
+ Odblokowywanie za pomocą odcisku palca, jeśli dodano skróty lub kiedy witryna jest już otwarta w %s.
+
+
+ Tryb ukryty
+
+ Ukrywanie stron podczas przełączania aplikacji i blokowanie wykonywania zrzutów ekranu.
+
+ Bezpieczeństwo
+
+ Wydajność
+ Blokowanie czcionek sieciowych
+
+ Może spowodować, że niektóre ikony lub obrazy będą niewidoczne
+
+ Blokowanie skryptów JavaScript
+
+ Strony mogą się szybciej wczytywać, ale mogą także nie działać poprawnie
+
+
+ Ustaw %1$s jako domyślną przeglądarkę
+
+ Mozilla
+ Przesyłanie danych o użyciu
+
+
+ Więcej informacji
+
+
+ Mozilla zbiera wyłącznie informacje niezbędne do udostępniania i ulepszania %1$s.
+
+
+ Zasady ochrony prywatności
+
+
+ Informacje licencyjne
+
+
+ Używane przez nas biblioteki
+
+
+ %s | Biblioteki open source
+
+
+ O programie %1$s
+
+
+ Zainstalowane wyszukiwarki
+
+
+ Wybierz wyszukiwarkę
+
+
+ Przywróć domyślne wyszukiwarki
+
+
+ + Dodaj inną wyszukiwarkę
+ Usuń wyszukiwarki
+ Usuń
+
+
+ Dodaj inną wyszukiwarkę
+
+ Wybierz preferowaną wyszukiwarkę:
+
+
+ Dodaj wyszukiwarkę
+
+ Nazwa wyszukiwarki
+ Używany ciąg wyszukiwania
+ Zapisz
+
+
+ Przykład: example.com/search/?q=%s
+
+ Dodano nową wyszukiwarkę.
+
+ Wpisz nazwę wyszukiwarki
+ Zainstalowana wyszukiwarka już używa tej nazwy.
+
+ Wpisz ciąg wyszukiwania
+
+ Upewnij się, że ciąg wyszukiwania zgadza się z formatem przykładu
+
+
+ Usuń tekst
+
+
+ Zamknij
+
+
+ Usuń historię przeglądania
+
+
+ Otwarte karty: %1$s
+
+
+ Zabezpieczone połączenie
+
+
+ Wczytywanie
+
+
+ Wczytano stronę
+
+
+ Więcej opcji
+
+
+ Więcej opcji
+
+
+ Przejdź do przodu
+
+
+ Ponownie wczytaj stronę
+
+
+ Przejdź wstecz
+
+
+ Zatrzymaj wczytywanie strony
+
+
+ Wróć do poprzedniej aplikacji
+
+
+ Liczba zablokowanych elementów śledzących
+
+
+ Blokowanie elementów śledzących
+
+ O prawach użytkownika
+
+ Otwórz odnośnik w innej aplikacji
+
+ Można wyjść z %1$s, aby otworzyć ten odnośnik w aplikacji %2$s.
+
+ Wyszukiwanie aplikacji mogącej otworzyć odnośnik
+
+ Żadna aplikacja na urządzeniu nie może otworzyć tego odnośnika. Można wyjść z %1$s, aby wyszukać odpowiednią aplikację w „%2$s”.
+
+ Wyjść z trybu prywatnego?
+
+
+ Ukończono „%1$s”
+
+
+ Otwórz
+
+
+
+
+
+
+
+
+
+
+ Dodano do skrótów
+
+ Nie odnaleziono serwera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zamknij
+
+
+
+ Witamy w przeglądarce %1$s
+
+
+ Szybka. Prywatna. Skupiona na celu.
+
+
+ Pierwsze kroki
+
+
+
+ %1$s nie jest jak inne przeglądarki
+
+
+ Usuwamy historię przeglądania po zamknięciu aplikacji, maksymalizując prywatność.
+
+
+
+ Ustaw przeglądarkę %1$s jako domyślną, aby chronić swoje dane przy każdym otwieranym odnośniku.
+
+
+ Ustaw jako domyślną przeglądarkę
+
+
+ Pomiń
+
+
+
+ Nowy poziom prywatności
+
+ Przejdź na wyższy poziom prywatności. Blokuj reklamy i inne elementy mogące Cię śledzić między witrynami i wydłużające wczytywanie się stron.
+
+
+ Wyszukuj po swojemu
+
+ Szukasz czegoś innego? Wybierz inną domyślną wyszukiwarkę w ustawieniach.
+
+
+ Dodawaj skróty do ekranu głównego
+
+ Szybko wracaj do ulubionych stron w %1$s. Po prostu wybierz „Dodaj do ekranu głównego” z menu.
+
+
+ Prywatność na co dzień
+
+ Ustaw %1$s jako domyślną przeglądarkę i korzystaj z zalet przeglądania prywatnego podczas otwierania stron z innych aplikacji.
+
+ OK
+ Pomiń
+ Dalej
+
+
+ —
+
+
+ Dodaj
+
+
+ Tak
+
+
+ Anuluj
+
+
+ Nie
+
+
+ Skrót będzie otwierany bez wzmocnionej ochrony przed śledzeniem
+
+
+ Sesja przeglądania prywatnego
+
+
+ Powiadomienia umożliwiają usunięcie sesji %1$s jednym dotknięciem. Nie trzeba otwierać aplikacji ani widzieć, co jest otwarte w przeglądarce.
+
+
+ Usuń historię przeglądania
+
+
+ Pobierz Firefoksa
+
+
+
+
+
+
+
+
+ MPL (Mozilla Public License) i innych licencji open source.]]>
+
+
+ Tutaj znajduje się więcej informacji na ten temat.]]>
+
+
+ licencji wolnego oprogramowania i open source.]]>
+
+
+ GPLv3 (GNU General Public License v3), opisany tutaj .]]>
+
+
+ Nazwa użytkownika
+ Hasło
+ Wyczyść
+
+
+
+ Zabezpieczone połączenie
+ Niezabezpieczone połączenie
+
+ Zweryfikowane przez: %1$s
+
+
+ Bezpieczeństwo strony
+ Adres już istnieje
+
+
+ Znajdź na stronie
+
+
+ Znajdź na stronie
+
+
+ %1$d/%2$d
+
+ %1$d z %2$d
+
+
+ Znajdź następny wynik
+
+ Znajdź poprzedni wynik
+
+ Zamknij wyszukiwanie na stronie
+
+
+
+
+ Wersja na komputery
+
+
+ Wersja na komputery
+
+
+ Skopiowano adres
+
+
+ Narzędzia dla programistów
+
+
+ Otwieranie odnośników w aplikacjach
+
+
+ Zaawansowane
+
+
+ Uprawnienia witryn
+
+
+ Ograniczanie informacji o ciasteczkach
+
+
+ Włączone
+
+
+ Wyłączone
+
+
+ Ograniczanie informacji o ciasteczkach
+
+
+ Wyświetla mniej informacji o ciasteczkach przez automatyczne odrzucanie próśb o ich zaakceptowanie, kiedy to możliwe.
+
+ -->
+ Ograniczanie informacji o ciasteczkach
+
+
+ Włączone na tej witrynie
+
+
+ Witryna obecnie nie jest obsługiwana
+
+
+ Wyłączone na tej witrynie
+
+
+ Ograniczanie informacji o ciasteczkach
+
+
+ Wyłączone na tej witrynie
+
+
+ Włączone na tej witrynie
+
+
+ Włączyć ograniczanie informacji o ciasteczkach na witrynie %1$s?
+
+
+ Wyłączyć ograniczanie informacji o ciasteczkach na witrynie %1$s?
+
+
+ %1$s usunie ciasteczka tej witryny i odświeży stronę. Usunięcie wszystkich ciasteczek może spowodować wylogowanie ze strony lub opróżnienie koszyka w sklepie.
+
+
+ %1$s może próbować automatycznie odrzucać prośby o akceptację ciasteczek.
+
+
+ Ta witryna nie jest obecnie obsługiwana przez funkcję ograniczania informacji o ciasteczkach. Czy chcesz poprosić nasz zespół o sprawdzenie tej witryny i dodanie jej obsługi w przyszłości?
+
+
+ Anuluj
+
+
+ Poproś o dodanie obsługi
+
+
+ Przesłano prośbę o dodanie obsługi witryny.
+
+
+ Przesłano prośbę o dodanie obsługi witryny.
+
+
+
+ %1$s próbuje odrzucać irytujące prośby o akceptację ciasteczek.\n\nZarządzaj preferencjami odrzucania w %2$s.
+
+ ustawieniach
+
+
+ Automatyczne odtwarzanie
+
+
+ Aby zezwolić:
+
+
+ 1. Przejdź do ustawień Androida
+
+
+ Uprawnienia]]>
+
+
+ Przejdź do ustawień
+
+
+ %1$s]]>
+
+
+ Aparat
+
+
+ Mikrofon
+
+
+ Położenie
+
+
+ Powiadomienia
+
+
+ Treści chronione przez DRM
+
+
+ Pytanie o zezwolenie
+
+
+ Zablokowane
+
+
+ Dozwolone
+
+
+ Zablokowane przez Androida
+
+
+ Zezwalaj na dźwięk i wideo
+
+
+ Blokuj tylko dźwięk
+
+
+ Zalecane
+
+
+ Blokuj dźwięk i wideo
+
+
+ Badania
+
+
+ Firefox może od czasu do czasu instalować i przeprowadzać badania.
+
+
+ Więcej informacji
+
+
+ Aplikacja zakończy działanie, aby zastosować zmiany
+
+
+ Usuń
+
+
+ Aktywne
+
+
+ Ukończone
+
+
+ Zdalne debugowanie przez USB/Wi-Fi
+
+
+ Odblokuj
+
+
+ Potwierdź za pomocą odcisku palca
+
+
+ Można użyć odcisku palca, aby kontynuować obecną sesję aplikacji.
+
+
+ Otwórz odnośnik w nowej sesji
+
+
+ Ikona odcisku palca
+
+
+ Nie rozpoznano odcisku palca. Spróbuj ponownie.
+
+
+ Palec ruszył się za szybko. Spróbuj ponownie.
+
+
+ Wyświetlać podpowiedzi wyszukiwania?
+
+
+ Aby uzyskać podpowiedzi, %1$s musi wysłać słowa wpisywane na pasku adresu do wyszukiwarki.
+
+
+ Nie
+
+
+ Tak
+
+
+ Część wyszukiwarek nie może wyświetlać podpowiedzi.
+
+
+ Zamknij
+
+
+
+
+ Strona zachowuje się nie tak, jak powinna?\n Spróbuj wyłączyć ochronę przed śledzeniem
+
+
+
+
+
+ Otwieraj każdy odnośnik w %1$s\n Ustaw %1$s jako domyślną przeglądarkę
+
+
+ Automatycznie uzupełniaj adresy najczęściej odwiedzanych stron\n Przytrzymaj dowolny adres na pasku adresu
+
+
+ Otwórz odnośnik w nowej karcie\n Przytrzymaj dowolny odnośnik na stronie
+
+
+ Wyłącz wskazówki na ekranie głównym
+
+
+ Otwarto nową kartę
+
+
+ Przejdź
+
+
+ Włączono tryb pełnoekranowy
+
+
+ Przechodzenie do odnośnika w nowej karcie bez pytania
+
+
+ Blokowanie potencjalnie niebezpiecznych i podejrzanych stron
+
+ Blokowanie zgłoszonych podejrzanych i stanowiących zagrożenie stron oraz stron ze złośliwym i niechcianym oprogramowaniem.
+
+
+ Tryb używania wyłącznie protokołu HTTPS
+
+
+ Automatycznie próbuje łączyć się ze stronami za pomocą protokołu szyfrowania HTTPS w celu zwiększenia bezpieczeństwa.
+
+
+ Wyjątki
+
+ Blokowanie treści jest wyłączone dla tych stron.
+
+ Usuń
+
+ Usuń wszystkie strony
+
+
+ Blokowanie ciasteczek
+
+
+ Czy chcesz blokować ciasteczka?
+
+
+ Karta uległa awarii
+
+ Wystąpił problem z tą kartą.
+
+ Prywatna przeglądarka nigdy nic nie zapisuje, więc nie można przywrócić tej karty.
+
+ Zamknij kartę
+
+
+
+
+
+ Zgłoś awarię Mozilli
+
+
+
+
+ Elementy śledzące zablokowane od %s
+
+ Treść
+
+ Reklamy
+
+ Społecznościowe
+
+ Statystyki
+
+ Wzmocniona ochrona przed śledzeniem
+
+ Ochrona jest wyłączona na tej witrynie
+
+ Ochrona jest włączona na tej witrynie
+
+ Połączenie jest zabezpieczone
+
+ Połączenie nie jest zabezpieczone
+
+ Elementy śledzące i skrypty do blokowania
+
+
+ Wstecz
+
+
+
+ Usuń
+
+
+ Zmień nazwę
+
+ Zmień nazwę
+
+
+ Nazwa skrótu
+
+
+ Zapisane i udostępnione obrazy <b>nie zostaną</b> usunięte po wyczyszczeniu historii %1$s
+
+
+
+ Motyw
+
+ Jasny
+
+ Ciemny
+
+ Według funkcji oszczędzania energii
+
+ Motyw urządzenia
+
+
+
+ Ta witryna nie obsługuje protokołu HTTPS
+
+
+ Więcej informacji
+ Można to zmienić w Ustawienia → Prywatność i bezpieczeństwo → Bezpieczeństwo.]]>
+
+
+ Niezabezpieczone połączenie
+
+
+
+ Jeśli łączono się wcześniej z tym serwerem, błąd może być tymczasowy.
+ ]]>
+
+
+ Ktoś może próbować podszywać się pod tę witrynę. Kontynuacja może być ryzykowna.
+
+ %1$s nie ufa certyfikatowi witryny %2$s , ponieważ jego wystawca jest nieznany, jest samopodpisany lub serwer nie przesyła właściwych certyfikatów pośrednich.
+ ]]>
+
+
+
+ Zamknij kartę
+
+
+
+ Uniemożliwiliśmy tej witrynie szpiegowanie Cię. Stuknij tarczę w dowolnej chwili, aby zobaczyć, co blokujemy.
+
+
+ Zamknij
+
+
+
+ Ochrona jest włączona!
+
+ Domyślne ustawienia zapewniają silną ochronę, ale łatwo można dostosować je do własnych potrzeb.
+
+ Zamknij
+
+
+ Stuknij tutaj, aby wyrzucić wszystko — historię, ciasteczka, wszystko — i zacząć od nowa na nowej karcie.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Zamknij
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widżet wyszukiwania
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Usunięto historię przeglądania! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Zacznij przeglądać w trybie prywatnym, a będziemy blokować elementy śledzące i inne złe rzeczy.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Następnym razem możesz szybciej zacząć przeglądanie prywatne za pomocą widżetu %1$s na ekranie głównym.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Dodaj widżet do ekranu głównego
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Dodano widżet do ekranu głównego
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ppl/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ppl/strings.xml
new file mode 100644
index 0000000000..38147cb312
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ppl/strings.xml
@@ -0,0 +1,1072 @@
+
+
+
+
+
+
+
+
+ Shikpulu
+
+ Yek nemi
+
+ Shikana
+
+
+ Shiktemu u shiktajkwilu ne dirección
+
+ Shipashalu ichtaka ka achtu.\nShipashalu. Shitapupu. Shikchiwa uksenpa.
+
+
+ Mupupujtuk kan tipashaluj yewa.
+ Ne tay tikitak ikman pulijtuk
+
+
+ Mupupujtuk kan tipashaluj tik ne ijitzun.
+
+
+ Shiktemu %1$s
+
+
+ Shiktakuli…
+
+
+ Shina tay te yek tik ne sitioj
+
+
+ Shiktapu tik %1$s
+
+
+ Shiktapu tik…
+
+
+ Shiktali tik Achtu Iswat
+
+
+ Shiksentali tik ne Atajos
+
+ Shikishti pal ne Atajos
+
+ Kan titajshitia
+ Taipanpa
+ Shinechpalewi
+ Ne tay mupal
+
+
+ Tapachiwiani tentzaktuk
+
+
+ Su tiksewia yajini, yaja weli kiekchiwa sejse uijkayu tik ne sitioj
+
+
+ Shiktentzakwa ne tay nemi ka kalijtik
+
+ Shiksewi pal tikekchiwa sejse sitioj
+
+
+ Metzwikilia %1$s
+
+
+ Shiktakuli tik
+
+
+ Ma mupupu kan nipashaluj yewa
+
+
+ Shiktapu
+
+
+ Shikpupu wan shiktapu
+
+
+ Shikpupu
+
+
+ Shikpupu kan nipashaluj yewa
+
+
+
+ Shikpupu wan shiktapu
+
+
+ Shikpupu wan shiktapu %1$s
+
+
+
+ Shiktemu tik Focus
+
+ Shiktemu tik Klar
+
+ Shiktemu tik Focus Beta
+
+ Shiktemu tik Focus Nightly
+
+
+ %1$s metztekipanultia.
+Shikwi kenha ken se ichtaka tajtachialis:
+
+ Shitatejtemu wan shipashalu tik ne app
+ Shiktentzakwa ne tapachiwiani (ush shitajshitia uksenpa pal tikajkawa tekiti ne tapachiwiani)
+ Shikpupu pal tikpulua cookies wan tay tiktemuj wan kan tipashaluj yewa
+
+
+Mozilla kichiwtuk ne %1$s. Tiknekit tikajsit tikpiat ne Wey Matat yek wan tapujtuk.
+Shikmatiuk
]]>
+
+
+ Ichtakachiwalis wan tamanawilis
+
+
+ Kan tikishpejpena ne tapachiwiani, cookies, wan dajdatuj
+
+
+ Shiktali ma senpa nemi achtu, ma temi isel
+
+
+
+
+ Ipanpa %1$s, tapalewilis
+
+
+ Timutajpiya ukchupi yek keman kinekit yawit mupan
+
+
+ Tay nemi ijtik ne Matapan
+
+
+ Shikpata ne aplicación
+
+
+ Muchi tajtaya
+
+
+ Ne tajtachialis achtutalijtuk, taketzalis
+
+
+ Kan tiksenputzua datuj wan ken tikwi
+
+ Shiktemu
+
+
+ Shikneshti tay niweliskia niktemua
+
+ %1$s yawi kititania tay titajkwilua tik ne barraj ipal dirección tik ne tepusyulu taishtemua
+
+
+ Ne achtutalijtuk
+
+
+ Tepusyulu tatemua
+
+
+ Timakaktuk
+
+
+ Sewtuk
+
+
+ URL temi isel
+
+
+ Kan tipajpashalua sejsenpa
+
+
+ Ma %s temit insel sejsé 450 URLs mujmumatituk tik ne barraj ipal dirección.
+
+
+ Pal ne sijsitioj tiktalia
+
+
+ Ma %s temit insel ne URLs taja mugustuj.
+
+
+ Shiktekimaka ne sijsitioj
+
+
+ Shiktekimaka ne sijsitioj
+
+
+ + Shiktali ne mu-URL
+
+
+ Ne mulistaj temi isel:
+
+
+ Shiktali uksé URL
+
+
+ Shiktali ne mu-URL
+
+
+ Shiktali ne mu-URL
+
+
+ Shiktali se ilpika pal temi isel
+
+
+ Cookies wan ne idatuj ne sitioj
+
+
+ Kan tikishpejpena ne datuj
+
+
+ Shikishti mu-URLs
+
+
+ Shikmatiuk
+
+
+ Shiktali u shina ken taja tikneki ma temi isel ne URLs.
+
+
+ Shiktali ne URL
+
+
+ Shiksalu ush shikishtuka ne URL
+
+
+ Machiut: mozilla.org
+
+
+ Machiut: example.com
+
+
+ Yankwik mu-URL mutalijtuk.
+
+
+ Shikishti
+
+
+ Shikishti
+
+
+ Shikpepeta uksenpa ne URL tiktajkwiluj.
+
+ Taketzalis
+
+ Achtutalijtuk sistemaj
+
+ Tay nemi ichtaka
+ Shiktentzakwa ne tapachiwiani ipal tanawatilis
+ Sejse tanawatilis kishpelwiat kan tipashalua, melka tesu tikpachua
+ Shiktentzakwa ne tapachiwiani tapepeta
+ Mukwi pal mukululua, muishpepeta wan mutamachiwa tay tikchiwa keman titatasa wan titamatilua
+ Shiktentzakwa ne tapachiwiani kimati kan tinejnemi
+ Muishtukatuk kan tipashalua pal metzishpelwia wan pal kineshtia ken yaja tekiti kenha ken ne bojbotón pal tiktakulia
+ Shiktentzakwa ujukseuk tapachiwiani ipal tay kipia kalijtik
+ Asu tiktimaka, sejseuk iswat welit tekitit ken te tikchia
+ Shiktentzakwa cookies
+
+
+ Tesu
+ Shiktentzakwa semaya ne cookies tapachiwiani ipal sejseuk
+ Shiktentzakwa semaya ne cookies ipal sejseuk
+
+ Shiktentzakwa ne cookies ipal sitioj nepanujtuk
+
+ Ejé
+
+
+ Shiktali ne itashkalchin mumapipil pal tikalaki tik ne aplicación
+
+
+ Shiktapu iwan mumapipiltashkal asu tiktalijtuk Shortcuts u keman se sitioj web tapujtuka tik %s.
+
+
+ Tay ichtaka
+
+ Shikinaya ijiswat kwak tikpata aplicación wan maka shikajkawa ma muishkupina ne pantallaj.
+
+ Tetajpialis
+
+ Shiktamachiwa ken tekiti
+ Shiktentzakwa ne tajkwilultzin ipal ne Matapan
+
+ Anka puliwiskiat ijiconoj wan kwijkwikwil
+
+ Shiktentzakwa JavaScript
+
+ Ne iswat weli mukimiltia uk talul, melka tesu weli tekiti ken taja te tikneki
+
+
+ Shikchiwa %1$s ken ne achtutalijtuk tajtachialis
+
+ Mozilla
+ Shiktitani ne datuj ipanpa keski seujti nipashalua
+
+
+ Shikmatiuk
+
+
+ Mozilla kineki kiululua sema tay timunekit pal tikmakat wan tikekchiwiliat muchi ne %1$s.
+
+
+ Tanawatilis ipal ne ichtaka
+
+
+ Ipanpa %1$s
+
+
+ Tejtepusyulu tatemuat ajajshitilijtuk
+
+
+ Shikishpejpena ne tepusyulu tatemua
+
+
+ Shimukwepa ka achtutalijtuk tepusyulu tatemua
+
+
+ + Shiktali seuk tepusyulu tatemua
+ Shikishti muchi ne tepusyulu tatemua
+ Shikishti
+
+
+ Shiktali seuk tepusyulu tatemua
+
+ Shikishpejpena mutepusyulu mugustuj
+
+
+ Shiktali se tepusyulu tatemua
+
+ Shiktemu ne itukay ne tepusyulu tatemua
+ Ne tajtaketzalis yawit mutemuat
+ Shikana
+
+
+ Machiut: machiut.com/search/?q=%s
+
+ Shiktali se yankwik tepusyulu tatemua.
+
+ Shiktajkwilu ne itukay ne tepusyulu tatemua
+ Seuk tepusyulu tatemua kipiaya ne sesan itukay.
+
+ Shiktajkwilu tay tikneki tiktemua
+
+ Shikpepeta ma ne tajtaketzalis tejtemujtuk kenha ken nemi ne Machiut
+
+
+ Shikpupu ne mutajkwilujtuk
+
+
+ Shikishti
+
+
+ Shikpupu kan tipashaluj yewa
+
+
+ Itzujtzun tajtapujtuk: %1$s
+
+
+ Muchi yek keman nikneki nimusalua
+
+
+ Mukimiltia
+
+
+ Ne sitioj matapan mukimiltijtuk
+
+
+ Ukchiupi taishpejpenalis
+
+
+ Botón pal nikita uksé taishpejpenalis
+
+
+ Shu ka ishpan
+
+
+ Ma mukimilti ne sitioj matapan uksenpa
+
+
+ Shimukwepa ka ipan
+
+
+ Makaya shikimilti ne sitioj matapan
+
+
+ Shimukwepa ne aplicación panutuk
+
+
+ Tapual ipal tapachiwiani tentzaktuk
+
+
+ Shiktentzakwa muchi ne tapachiwiani
+
+ Tay mupal
+
+ Shiktapu ne ilpika tik seuk aplicación
+
+ Tiweli tikisa tik %1$s pal tiktapua ini ilpika tik %2$s.
+
+ Shikajsi se aplicación weli kitapua ne ilpika
+
+ Te kanaj se aplicación weli kitapua ini ilpika. Tiweli tikisa tik %1$s pal tiktemua %2$s ipal se aplicación weli kitapua.\n\n
+
+ Tikneki tikisa tik ne ichtaka pashalulis?
+
+
+ %1$s tamik
+
+
+ Shiktapu
+
+
+
+
+
+
+
+
+
+ Ne servidor te muajsik
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shiktzakwa
+
+
+
+ Yek shiajsi %1$s
+
+
+ Talul.Ichtaka.Te timupulua.
+
+
+ Shipewa
+
+
+
+ %1$s tesu kenha ne seuk tajtachialis
+
+
+ Tejemet tikpupuat tay tikitztuk keman tiktzakwa ne app pal tipashalua ichtaka sejsenpa.
+
+
+
+ Shikwepa %1$s ne achtutalijtuk pal tikmanawi mudatoj iwan sejsé ilpika tiktapua.
+
+
+ Shiktali ken ne tajtachialis achtutalijtuk
+
+
+ Te nikneki nikita
+
+
+ Shikchikawa ne muichtakanemilis
+
+ Tiweli tipashalua ichtaka te kenha ken ne sejseuk. Shiktentzakwa muchi ne tanawatilis ush muchi tay weliskia metzpachiwia keman tikita ne sitioj wan tay kichiwa yujyulik kan mukimiltiat ne ijiswat.
+
+
+ Ken tikneki titatemua
+
+ Tiktemua seuk? Shikajshiti ukse achtutalijtuk tepusyulu tatemua.
+
+
+ Shiktali tamelawka tik ne achtu iswat
+
+ Shimukwepa tik ne sitioj kan mugustuj tipashalua %1$s talul. Semaya shikishpejpena \"Shiktali tik ne Achtu Iswat\" itech ne menú %1$s.
+
+
+ Shinemi ichtaka mujmusta
+
+ Shikajshiti %1$s ken tajtachialis mupal wan tikitas ka tiweliskia tipashalua ichtaka kwak tiktapua ijiswat tik ujukse aplicaciones.
+
+ Yek nemi, nikmatiaya!
+ Te nikneki nikita
+ Ne witz
+
+
+ -
+
+
+ Shiktali
+
+
+ EJE
+
+
+ Shikajkawa
+
+
+ TESU
+
+
+ Ne ujti kutu mutapus iwan Enhanced Tracking Protection sewijtuk
+
+
+ Tanamikilis pal tipashalua ichtaka
+
+
+ Tajtanawatilis metzajkawat tikpupua mu%1$s tanamikilis kan tiktasa iwan mumapipil. Te nemi pal tiktapu ne aplicacion ush pal tikita tay panu tik ne tajtachialis.
+
+
+ Shikpupu kan tipashaluj yewa
+
+
+ Shiktemulti Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License wan sejseuk licenciaj iwan walajkewalis tapujtuk.]]>
+
+
+ nikan.]]>
+
+
+ licenciaj.]]>
+
+
+ GNU Licenciaj General Public v3, wan weli muita nikan .]]>
+
+
+ Mutukay usuarioj
+ Ichtaka taketzalis
+ Shikpupu
+
+
+
+ Timusalua wan tinejmachnemi
+ Timusalua wan tesu nejmachnemi
+
+ Kitak yek %1$s
+
+
+ Tetajpialis kan tipashalua
+ URL nemia
+
+
+ Shikajsi tik ne iswat
+
+
+ Shikajsi tik ne iswat
+
+
+ %1$d/%2$d
+
+ %1$d ipal %2$d
+
+
+ Shikajsi tay yawi kisa nemanha
+
+ Shikajsi tay kiski yakin
+
+ Maka shikwi tay muajsik tik ne iswat
+
+
+
+
+ Shikita version tajkwilultapech
+
+
+ Ne sitioj ipal ne desktop
+
+
+ URL kupintuk
+
+
+ Tajtaiwan pal titakejketza
+
+
+ Shiktapu ne ijilpika tik ne apps
+
+
+ Tapanawilis
+
+
+ Tay tikajkawa ma muchiwa tik ne sitioj
+
+
+ Takashawani ipal ne ibanner ne cookie
+
+
+ Timakaktuk
+
+
+ Sewtuk
+
+
+ Takashawani ipal ne ibanner ne cookie
+
+
+ Shikita chupi bajbaners kwak mutawelkwepilia isel ne itajtanilis ne cookie, su weli.
+
+ -->
+ Takashawani ipal ne ibanner ne cookie
+
+
+ SHIKTIMAKA tik ini sitioj
+
+
+ SHIKSEWI tik ini sitioj
+
+
+ Takashawani ipal ne ibanner ne cookie
+
+
+ SHIKSEWI tik ini sitioj
+
+
+ SHIKTIMAKA tik ini sitioj
+
+
+ Shiktimaka Cookie Banner Reduction ipal %1$s?
+
+
+ Shiksewi Cookie Banner Reduction ipal %1$s?
+
+
+ %1$s yawi kipupua ne icookies ini sitioj wan yawi kiyankwilia ne iswat. Asu tikpupua muchi ne cookies, mutukey yawi mukishtia u yawi tuyawi muchikiw kan nemi tay tikneki tikua.
+
+
+ %1$s weli kiejekua isel kitawelkwepilia ne itajtanilis ne cookie.
+
+
+ %1$s kiejekua kitawelkwepilia ne itajtanilis ne cookie pal kikutamima ne ipajpanti ne cookie takakasua.\n\nShiktukti ken mugustuj ne ipanti ne cookie tik %2$s.
+
+
+ tachijchiwalis
+
+
+ Ma mupewalti isel
+
+
+ Pal tikajkawa:
+
+
+ 1. Shu tik ne tajtachiwalis Android
+
+
+ Panulis]]>
+
+
+ Shu tik tachijchiwalis
+
+
+ %1$s pal nemi TIMAKATUK]]>
+
+
+ Cámaraj
+
+
+ Micrófonoj
+
+
+ Kan muajsi
+
+
+ Tanawatilis
+
+
+ Tay kipia kalijtik ne DRM tatuktia
+
+
+ Shiktajtani ma muajkawa
+
+
+ Mutentzaktuk
+
+
+ Muajkawtuk
+
+
+ Kitentzaktuk Android
+
+
+ Shikajkawa ma tzalani wan ma muita
+
+
+ Te ma mukaki
+
+
+ Muyeknekituk
+
+
+ Te ma mukaki wan te ma muita
+
+
+ Tapepetalis
+
+
+ Firefox weliskia kitalia wan kipewaltia tapepetalis kemanian.
+
+
+ Shikmatiuk
+
+
+ Ne aplicación mutzakwas pal mupata yek.
+
+
+ Shikishti
+
+
+ Activoj
+
+
+ Muajshitijtuk
+
+
+ Shitayekchiwa wejka iwan ne USB/Wi-Fi
+
+
+ Shikchululti
+
+
+ Shina kia iwan ne ikwikwil mumapipiltashkal
+
+
+ Tiweli tikpachua ne ikwikwil mumapipiltashkal pal naka tik ne isesión ne aplicación sanuk.
+
+
+ Shiktapu ne Ilpika tik Yankwik Tanamikilis
+
+
+ Ne iíconoj ne ikwikwil ne mapipiltashkal
+
+
+ Ne mapipiltashkal tesu muishmati. Shikejeku uksenpa.
+
+
+ Tikpanultij mumapipil sujsul talul. Shikejeku uksenpa.
+
+
+ Shikneshti tay niweliskia niktemua
+
+
+ Pal tikita tanawatilis, %1$s kineki kititania tay tikijkwilua tik ne ibarraj ne dirección tik ne tepusyulu tatemua.
+
+
+ Tesu
+
+
+ Eje
+
+
+ Sejse tepusyulu tatemuat te welit tanawatiat.
+
+
+ Shikishti
+
+
+
+
+ Kan titajtachia te tekiti ken tikneki?\n Anka su tiksewia Tamanawalis itech Tapachiwiani
+
+
+ Shiktalili tik Achtu Iswat]]>
+
+
+ Shiktapu sejse ilpika %1$s\n Shikchiwa %1$s ken ne achtutalijtuk tajtachialis
+
+
+ Ma temit isel ne URLs ipal kan taja sejsenpa tipashalua\n Shikpajpachu ne URL tay tikneki ijtik ne barraj ipal dirección
+
+
+ Shiktapu se ilpika tik ukse itzun\n Shikpajpachu yek ne ilpika taja tikneki tik se iswat
+
+
+ Ma musewi ne tajtanawatial ijpak ne achtu iswat
+
+
+ Se yankwik itzun tapuik
+
+
+ Shikpata
+
+
+ Tikalaki ne modoj pantallaj patawak
+
+
+ Shiktapu ne ilpika melaktik tik ukse itzun
+
+
+ Ma mutentzakwakan ne sijsitioj tesajsay wan tashijshikuani
+
+ Shiktentzakwa ne sijsitioj tesajsay wan tashijshikuani, sitioj malware. wan sitioj ipal software tesu tikneki.
+
+
+ Modoj HTTPS-Only
+
+
+ Kiejekua musalua isel tech sijsitioj, kikwi ne protocoloj ipal ne inashtuk HTTPS pal mumanawia uk yek.
+
+
+ Tay te nemi muishtukatuk
+
+ Taja tiksewij Tatentzakwalis ipal Tay Nemi ka Ijtik ipal ne isijsitioj ne Wey Matat.
+
+ Shikishti
+
+ Shikishti muchi ne sitioj ipal ne matapan
+
+
+ Shiktentzakwa ne cookies
+
+
+ Tiknekiskia tiktentzakwa ne cookies?
+
+
+ Ne itzun kunaktuk nemi
+
+ Shitapupulwi. Saman tikpiat uijkayu iwan ini itzun.
+
+ Ika se tajtachialis ichtaka, tejemet te keman tikanat nian talneshtiat ini itzun.
+
+ Shiktzakwa ne itzun
+
+
+
+
+
+ Shikilwi Mozilla ka mukunakak
+
+
+
+
+ Tajtapachiwiani tentzaktiwit an %s
+
+ Tay kipia kalijtik
+
+ Publicidad
+
+ Social
+
+ Analíticaj
+
+ Timutajpiya ukchupi yek keman kinekit yawit mupan
+
+
+ Tajpialis SEWTUK ipal ini sitioj
+
+ Ne tajtapialis TIMAKATIWIT ipal ini sitioj
+
+ Tasalulis mutajpia
+
+ Tasalulis tesu mutajpia
+
+ Ne tajtapachiwiani wan scripts yawit mutentzakwat
+
+
+ Shimukwepa
+
+
+
+ Shikishti
+
+
+ Shiktukeyti uksenpa
+
+ Shiktukeyti uksenpa
+
+ Ne itukey ne ujti kutu
+
+
+ Ne kwikwil tikanki wan tiktakulij <b>tesu yawit</b> mupupuat keman tikpupua %1$s tay tiktemuj yewa
+
+
+
+ Temaj
+
+
+ Chipawak
+
+ Kunyua
+
+ Kitalijtuk ika kiana bateríaj
+
+ Shiktali ne itemaj ne dispositivoj
+
+
+ Ini sitioj tesu kiselia HTTPS
+
+
+ Shitamati ukchupi
+ Shikpata ini tachijchiwalis tik Tachijchiwalis > Ichtakayu & Nejmachnemilis> Nejmachnemilis.]]>
+
+
+ Ne Musalulis tesu nejmach
+
+
+
+ Asu timusalujtuk yek itech ini servidor ikman, ne tajtakul anka ishtuna chupi.
+ ]]>
+
+
+ Se akaj anka kiejekua tashijshikua mukwepa ne sitioj wan asu tipanu weli tesajsay.
+
+ %1$s tesu kichiwa confianzaj tik %2$s ika ne emisor pal ne certificadoj tesu mumati, ne certificadoj kijkwiluj ne ifirmaj isel wan ne servidor tesu kititania ne yejyek cejcertificadoj intermedios.
+ ]]>
+
+
+
+ Shiktzakwa ne itzun
+
+
+
+ Tikinhitzket! Tikinhitzket te ma metzishpelwikan. Shikpachu ne chimal keman tikneki pal tikita tay tikitzket.
+
+
+ Shiktzakwa tentzin nejnesi.
+
+
+
+ Timutajpishtuk!
+
+ Tiweli tikajshitia ini ken achtutalijtuk pal timutajpia yek. Wan te uij titajshitia pal tichiwa tay timuneki.
+
+ Shikishti
+
+
+ Shikpachu nikan pal tikmima muchi — historial, cookies, muchi — wan shipewa uksenpa tik uksé itzun.
+
+
+
+
+ Shiktzakwa
+
+
+ Widget tatemua
+
+
+ Muhistorial mupupujtuka!🎉
+
+
+ Shikpewalti musesión tajtachialis ichtaka, wan tejemet tiu tiktentzakwa ne tapajpachiwiani wan seuk te yejyek kwak tipashalua.
+
+
+ Tejemet timetzajkawiliat iwan ne tajtachialis ichtaka wan shipewa talul uksenpa iwan ne widget ipal %1$s tik ne achtu iswat.
+
+
+ Shiktali se widget tik ne achtu iswat
+
+
+ Ne widget mutalijtuk tik ne achtu iswat
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-pt-rBR/strings.xml b/mobile/android/focus-android/app/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000000..31a8fe7df3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,1122 @@
+
+
+
+
+
+
+
+
+ Cancelar
+
+ OK
+
+ Salvar
+
+
+ Pesquise ou digite um endereço
+
+ Navegação privativa automática.\nNavegar. Limpar. Repetir.
+
+
+ Seu histórico de navegação foi apagado.
+ O histórico de navegação foi limpo
+
+
+ O histórico de navegação da aba foi apagado.
+
+
+ Pesquisar %1$s
+
+
+ Compartilhar…
+
+
+ Relatar problema no site
+
+
+ Abrir no %1$s
+
+
+ Abrir no…
+
+
+ Adicionar à tela do dispositivo
+
+
+ Adicionar aos atalhos
+
+ Remover dos atalhos
+
+
+ Configurações
+ Sobre
+ Ajuda
+ Seus direitos
+
+
+ Rastreadores bloqueados
+
+
+ Desativar pode resolver problemas de sites
+
+
+ Bloqueio de conteúdo
+
+ Desative para corrigir alguns sites
+
+
+ Tecnologia %1$s
+
+
+ Compartilhar via
+
+ Limpar histórico de navegação?
+ Toque ou remova esta notificação para limpar com segurança o histórico de navegação.
+
+
+ Toque ou deslize esta notificação para limpar com segurança o histórico de navegação.
+
+ Limpar histórico de navegação
+
+
+ Abrir
+
+
+ Limpar e abrir
+
+
+ Limpar
+
+
+ Limpar histórico de navegação
+
+
+
+ Limpar e abrir
+
+
+ Limpar e abrir %1$s
+
+
+
+ Pesquisar no Focus
+
+ Pesquisar no Klar
+
+ Pesquisar no Focus Beta
+
+ Pesquisar no Focus Nightly
+
+
+ Assuma o controle com o %1$s.
+Use para navegação privativa:
+
+ Pesquisa e navegação direto no aplicativo.
+ Bloqueio de rastreamento (ou mude as configurações para permitir).
+ Limpeza de cookies e histórico de pesquisa e navegação.
+
+
+O %1$s é produzido pela Mozilla. Nossa missão é promover uma internet saudável e aberta.
+Saiba mais
]]>
+
+
+ Privacidade e segurança
+
+
+ Rastreamento, cookies, escolha de dados
+
+
+ Definição de padrão, preenchimento automático
+
+
+
+
+ Informações sobre o %1$s, suporte
+
+
+ Proteção aprimorada contra rastreamento
+
+
+ Conteúdo web
+
+
+ Alternar entre aplicativos
+
+
+ Geral
+
+
+ Navegador padrão, idioma
+
+
+ Coleta e uso de dados
+
+ Pesquisa
+
+
+ Receber sugestões de pesquisa
+
+ Se ativar, o %1$s envia o que você escreve na barra de endereços para o mecanismo de pesquisa escolhido.
+
+
+ Padrão
+
+
+ Mecanismo de pesquisa
+
+
+ Ativado
+
+
+ Desativado
+
+
+ Preenchimento automático de URLs
+
+
+ Em sites populares
+
+
+ Ative para o %s completar automaticamente mais de 450 URLs populares na barra de endereços.
+
+
+ Em sites que você adicionar
+
+
+ Ative para o %s completar automaticamente suas URLs preferidas.
+
+
+ Gerenciar sites
+
+
+ Gerenciar sites
+
+
+ + Adicionar URL personalizada
+
+
+ Sua lista de preenchimento automático:
+
+
+ Adicionar URL
+
+
+ Adicionar URL personalizada
+
+
+ Adicionar URL personalizada
+
+
+ Adicionar link ao preenchimento automático
+
+
+ Cookies e dados de sites
+
+
+ Escolha de dados
+
+
+ Remover URLs personalizadas
+
+
+ Saiba mais
+
+
+ Adicione e gerencie URLs personalizadas de preenchimento automático.
+
+
+ URL a adicionar
+
+
+ Cole ou digite uma URL
+
+
+ Exemplo: mozilla.org
+
+
+ Exemplo: example.com
+
+
+ Adicionada nova URL personalizada.
+
+
+ Remover
+
+
+ Remover
+
+
+ Verifique novamente a URL digitada.
+
+ Idioma
+
+ Padrão do sistema
+
+ Privacidade
+ Bloquear rastreadores de anúncios
+ Alguns anúncios rastreiam visitas aos sites, mesmo que você não clique nos anúncios
+ Bloquear rastreadores analíticos
+ São usados para coletar, analisar e medir atividades como toque e deslizamento
+ Bloquear rastreadores de mídias sociais
+ Incorporados em sites para rastrear suas visitas e exibir funcionalidades como botões de compartilhar
+ Bloquear outros rastreadores
+ Este bloqueio pode causar comportamento inesperado em algumas páginas
+ Bloquear cookies
+
+
+ Não, obrigado
+ Bloquear somente cookies rastreadores de terceiros
+ Bloquear somente cookies de terceiros
+
+ Bloquear cookies entre sites
+ Sim, por favor
+
+
+ Usar impressão digital para desbloquear o aplicativo
+
+
+ Desbloqueie usando impressão digital se você adicionou atalhos ou quando um site já estiver aberto no %s.
+
+
+ Furtividade
+
+ Ocultar páginas web ao mudar de aplicativo e bloquear capturas de tela.
+
+ Segurança
+
+ Desempenho
+ Bloquear fontes web
+
+ Pode resultar em falta de ícones ou imagens
+
+ Bloquear JavaScript
+
+ As páginas podem carregar mais rápido, mas também podem ter comportamento inesperado
+
+
+ Definir o %1$s como navegador padrão
+
+ Mozilla
+ Enviar dados de uso
+
+
+ Saiba mais
+
+
+ A Mozilla se empenha para coletar somente o necessário para fornecer e melhorar o %1$s para todos.
+
+
+ Aviso de privacidade
+
+
+ Informações de licenciamento
+
+
+ Bibliotecas que usamos
+
+
+ %s | Bibliotecas de código aberto
+
+
+ Sobre o %1$s
+
+
+ Mecanismos de pesquisa instalados
+
+
+ Escolha o mecanismo de pesquisa
+
+
+ Restaurar mecanismos de pesquisa padrão
+
+
+ + Adicionar outro mecanismo de pesquisa
+ Remover mecanismos de pesquisa
+ Remover
+
+
+ Adicionar outro mecanismo de pesquisa
+
+ Selecione seu mecanismo preferido:
+
+
+ Adicionar mecanismo de pesquisa
+
+ Nome do mecanismo de pesquisa
+ Termo de pesquisa a ser usado
+ Salvar
+
+
+ Exemplo: example.com/search/?q= %s
+
+ Adicionado novo mecanismo de pesquisa.
+
+ Digite o nome do mecanismo de pesquisa
+ Um mecanismo de pesquisa instalado já está usando esse nome.
+
+ Digite um código de pesquisa
+
+ Verifique se o código de pesquisa corresponde ao formato do exemplo
+
+
+ Limpar campo
+
+
+ Dispensar
+
+
+ Limpar histórico de navegação
+
+
+ Abas abertas: %1$s
+
+
+ Conexão segura
+
+
+ Carregando
+
+
+ Site carregado
+
+
+ Mais opções
+
+
+ Botão de outras opções
+
+
+ Próxima página
+
+
+ Recarregar o site
+
+
+ Página anterior
+
+
+ Interromper carregamento do site
+
+
+ Retornar ao aplicativo anterior
+
+
+ Número de rastreadores bloqueados
+
+
+ Bloquear rastreadores
+
+ Seus direitos
+
+ Abrir link em outro app
+
+ Você pode sair do %1$s para abrir este link no %2$s.
+
+ Encontre um aplicativo que possa abrir o link
+
+ Nenhum aplicativo do seu dispositivo consegue abrir este link. Você pode sair do %1$s para procurar no %2$s um aplicativo compatível.
+
+ Sair da navegação privativa?
+
+
+ %1$s concluído
+
+
+ Abrir
+
+
+
+
+
+
+
+
+
+
+ Adicionado aos atalhos!
+
+ Servidor não encontrado
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fechar
+
+
+
+ Boas-vindas ao %1$s
+
+
+ Rápido. Privativo. Sem distrações.
+
+
+ Introdução
+
+
+
+ O %1$s não é como outros navegadores
+
+
+ Limpamos o histórico quando você fecha o aplicativo para privacidade extra.
+
+
+
+ Torne o %1$s o padrão para proteger seus dados a cada link que você abrir.
+
+
+ Definir como navegador padrão
+
+
+ Pular
+
+
+
+ Potencialize sua privacidade
+
+ Leve a navegação privativa ao próximo nível. Bloqueie anúncios e outros conteúdos que podem rastrear você entre sites e aumentar o tempo de carregamento de páginas.
+
+
+ Pesquise à sua maneira
+
+ Procurando algo diferente? Use as configurações para escolher outro mecanismo de pesquisa padrão.
+
+
+ Adicione atalhos na sua tela inicial
+
+ Volte rapidamente a seus sites preferidos no %1$s. Basta usar \"Adicionar à tela inicial\" no menu do %1$s.
+
+
+ Torne privacidade um hábito
+
+ Defina o %1$s como navegador padrão e tenha os benefícios da navegação privativa ao abrir páginas a partir de outros aplicativos.
+
+ Ok, entendi
+ Pular
+ Avançar
+
+
+ -
+
+
+ Adicionar
+
+
+ SIM
+
+
+ Cancelar
+
+
+ NÃO
+
+
+ O atalho será aberto com a proteção aprimorada contra rastreamento desativada
+
+
+ Sessão de navegação privativa
+
+
+ Notificações permitem limpar a sessão do %1$s com um único toque. Você não precisa abrir o aplicativo ou ver o que está no navegador.
+
+
+ Limpar o histórico de navegação
+
+
+ Baixar o Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License e outras licenças de código aberto.]]>
+
+
+ aqui.]]>
+
+
+ licenças livres e de código aberto.]]>
+
+
+ aqui sob a GNU General Public License v3 .]]>
+
+
+ Nome de usuário
+ Senha
+ Limpar
+
+
+
+ Conexão segura
+ Conexão não segura
+
+ Homologado por: %1$s
+
+
+ Segurança do site
+ A URL já existe
+
+
+ Procurar na página
+
+
+ Procurar na página
+
+
+ %1$d/%2$d
+
+ %1$d de %2$d
+
+
+ Próximo resultado
+
+ Resultado anterior
+
+ Descartar procura na página
+
+
+
+
+ Site do desktop
+
+
+ Site do desktop
+
+
+ URL copiada
+
+
+ Ferramentas de desenvolvimento
+
+
+ Abrir links em aplicativos
+
+
+ Avançado
+
+
+ Permissões de sites
+
+
+ Redução de avisos de cookies
+
+
+ Ativado
+
+
+ Desativado
+
+
+ Redução de avisos de cookies
+
+
+ Veja menos avisos, rejeitando automaticamente solicitações de cookies, quando possível.
+
+ -->
+ Redução de avisos de cookies
+
+
+ ATIVADO neste site
+
+
+ Atualmente não funciona neste site
+
+
+ DESATIVADO neste site
+
+
+ Redução de avisos de cookies
+
+
+ DESATIVADO neste site
+
+
+ ATIVADO neste site
+
+
+ Ativar redução de avisos de cookies em %1$s?
+
+
+ Desativar redução de avisos de cookies em %1$s?
+
+
+ O %1$s irá limpar os cookies deste site e atualizar a página. Limpar todos os cookies pode encerrar a sessão de acesso no site ou esvaziar carrinhos de compras.
+
+
+ O %1$s pode tentar rejeitar solicitações de cookies automaticamente.
+
+
+ A redução de avisos de cookies atualmente não funciona neste site. Quer pedir à nossa equipe para revisar este site para funcionar no futuro?
+
+
+ Cancelar
+
+
+ Pedir para funcionar
+
+
+ Enviado pedido para funcionar neste site.
+
+
+ Enviado pedido para funcionar neste site.
+
+
+
+ O %1$s tenta rejeitar solicitações de cookies para descartar avisos chatos de cookies.\n\nGerencie as preferências de avisos de cookies nas %2$s.
+
+
+ configurações
+
+
+ Reprodução automática
+
+
+ Para permitir:
+
+
+ 1. Acesse as configurações do Android
+
+
+ Permissões]]>
+
+
+ Ir para configurações
+
+
+ %1$s para PERMITIDO]]>
+
+
+ Câmera
+
+
+ Microfone
+
+
+ Localização
+
+
+ Notificações
+
+
+ Conteúdo controlado por direitos autorais
+
+
+ Perguntar se deve permitir
+
+
+ Bloqueado
+
+
+ Permitido
+
+
+ Bloqueado pelo Android
+
+
+ Permitir áudio e vídeo
+
+
+ Bloquear apenas áudio
+
+
+ Recomendado
+
+
+ Bloquear áudio e vídeo
+
+
+ Estudos
+
+
+ O Firefox pode instalar e executar estudos de vez em quando.
+
+
+ Saiba mais
+
+
+ O aplicativo irá fechar para aplicar as alterações
+
+
+ Remover
+
+
+ Ativo
+
+
+ Concluído
+
+
+ Depuração remota via USB/WiFi
+
+
+ Desbloquear
+
+
+ Confirme usando sua impressão digital
+
+
+ Você pode usar sua impressão digital para continuar a sessão atual no aplicativo.
+
+
+ Abrir link em nova sessão
+
+
+ Ícone de impressão digital
+
+
+ Impressão digital não reconhecida. Tente novamente.
+
+
+ Movimento de dedo rápido demais. Tente novamente.
+
+
+ Mostrar sugestões de pesquisa?
+
+
+ Para receber sugestões, o %1$s precisa enviar o que você escreve na barra de endereços para o mecanismo de pesquisa.
+
+
+ Não
+
+
+ Sim
+
+
+ Alguns mecanismos de pesquisa não podem mostrar sugestões.
+
+
+ Descartar
+
+
+
+
+ Um site não está se comportando como esperado?\n Experimente desativar a proteção contra rastreamento
+
+
+ Adicionar à tela inicial]]>
+
+
+ Abra todos os links no %1$s\n Defina o %1$s como navegador padrão
+
+
+ Preenchimento automático de URLs dos sites que você mais usa\n Mantenha o dedo sobre qualquer URL na barra de endereços
+
+
+ Abra um link em nova aba\n Mantenha o dedo sobre qualquer link em uma página
+
+
+ Desativar dicas na tela inicial
+
+
+ Nova aba aberta
+
+
+ Alternar
+
+
+ Entrando no modo de tela inteira
+
+
+ Mudar imediatamente para link em nova aba
+
+
+ Bloquear sites potencialmente perigosos e maliciosos
+
+ Bloquear sites denunciados como enganosos e maliciosos, sites com malware e sites com contendo software indesejado.
+
+
+ Modo somente HTTPS
+
+
+ Tenta se conectar com sites usando automaticamente o protocolo de criptografia HTTPS para maior segurança.
+
+
+ Exceções
+
+ Você desativou o bloqueio de conteúdo nestes sites.
+
+ Remover
+
+ Remover todos os sites
+
+
+ Bloquear cookies
+
+
+ Você quer bloquear cookies?
+
+
+ A aba travou
+
+ Desculpe, há um problema nesta aba.
+
+ Sendo um navegador privativo, ele nunca salva e não pode restaurar esta aba.
+
+ Fechar aba
+
+
+
+
+
+ Enviar relatório de travamento para a Mozilla
+
+
+
+
+ Rastreadores bloqueados desde %s
+
+ Conteúdo
+
+ Propaganda
+
+
+ Mídias sociais
+
+
+ Analíticos
+
+ Proteção aprimorada contra rastreamento
+
+ As proteções estão DESATIVADAS neste site
+
+ As proteções estão ATIVADAS neste site
+
+ A conexão é segura
+
+ Conexão não segura
+
+ Rastreadores e scripts a bloquear
+
+
+ Voltar
+
+
+
+ Remover
+
+
+ Renomear
+
+ Renomear
+
+
+ Nome do atalho
+
+
+ Imagens salvas e compartilhadas <b>não são</b> excluídas quando você apaga o histórico do %1$s
+
+
+
+ Tema
+
+ Claro
+
+ Escuro
+
+ Definido pela economia de bateria
+
+ Acompanhar tema do dispositivo
+
+
+
+ Este site não oferece suporte a HTTPS
+
+
+ Saiba mais
+ Altere esta configuração em Configurações > Privacidade e segurança > Segurança.]]>
+
+
+ Conexão não segura
+
+
+
+ Se você já se conectou a este servidor com sucesso antes, o erro pode ser temporário.
+ ]]>
+
+
+ Alguém pode estar tentando se passar pelo site, continuar pode ser arriscado.
+
+ O %1$s não confia em %2$s porque o emissor do certificado é desconhecido, ou o certificado é autoassinado, ou o servidor não está enviando os certificados intermediários corretos.
+ ]]>
+
+
+
+ Fechar aba
+
+
+
+ Pegamos eles! Impedimos que este site espione você. Toque no escudo quando quiser para ver o que estamos bloqueando.
+
+
+ Fechar painel
+
+
+
+ Você está protegido!
+
+ Estas configurações padrão oferecem proteção forte. Mas é fácil ajustar as configurações para atender às suas necessidades específicas.
+
+ Descartar
+
+
+ Toque aqui para excluir tudo (histórico, cookies, tudo) e começar de novo em uma nova aba.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Fechar
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget de pesquisa
+
+ !-- This is the title of promote search widget dialog. -->
+
+ O histórico de navegação foi limpo! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Inicie uma sessão de navegação privativa e bloquearemos rastreadores e outras coisas ruins à medida que você avança.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Deixamos você com sua navegação privativa, mas na próxima vez pode começar mais rápido usando o widget do %1$s na tela inicial.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Adicionar widget à tela inicial
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget adicionado à tela inicial
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml b/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml
new file mode 100644
index 0000000000..9d72cf1542
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-quc/strings.xml
@@ -0,0 +1,709 @@
+
+
+
+
+
+
+
+
+ Uq\'atexik
+
+ Uk\'amik
+
+ Uk\'olik
+
+
+ Utzukuxik on rokisaxik kemriqonib\'al
+
+ Nik\'onem rech echeb\'alil pa utukelam.\nUtzukuxik. Uyujik. Ukamulixik.
+
+
+ Le atzukub\'al rech b\'anatal kanoq xchupisaxik.
+
+ Xchupisax le xajtanem rech nik´onem
+
+
+ Le xajtajem rech nik´onel rech woktz´ib´anel xchupisaxik.
+
+
+ Chatzukuj chech %1$s
+
+
+ Ukomone\'xik…
+
+
+ Ub\'ixikil rech k\'axk\'olil rech wokk\'olib\'al web\'
+
+
+ Ujaqik pa %1$s
+
+
+ Ujaqil pa…
+
+
+ Chaya\' pa umajib\'al q\'axwach
+
+
+ Chaya’ pa ramb’al b’e
+
+ Chawelesaj pa ramb’al b’e
+
+
+ Taq wiqitajem
+ Chi rij
+ Tob\'anem
+ Taq Aya\'talil
+
+
+ Q\'atetal taq tzukub\'al
+
+
+ We kchupisax wa’ wene kusuk’umaj jujun taq uk’axk’olil
+
+
+ Uq‘atexik upam chak
+
+ Chachupu’ chech usuk’maxik jujun taq uk’olib’al web’
+
+
+ Ya\'om uchuq\'ab\' rumal %1$s
+
+
+ Ukomone\'xik pa
+
+
+ Uchupik nik\'ob\'al rech b\'antal kanoq
+
+
+ Ujaqik
+
+
+ Uchupik chi\'l ujaqik
+
+
+ Uyujik
+
+
+ Uchupik nik\'ob\'al rech b\'antal kanoq
+
+
+
+ Chachupu´ k´ate k´u ri´ chatzija´
+
+
+ Chachupu´ k´ate k´u ri´ chatzija´ %1$s
+
+
+ Chatzukuj pa Focus
+
+ Chatzukuj pa Klar
+
+ Chatzukuj pa Focus Beta
+
+ Chatzukuj pa Focus Nightly
+
+
+ %1$s kuya´ rilawachixik chi awech.
+Chakojo´ pacha´ nik´ob´al rech echeb´alil:
+
+ Chatzukuj chi´l chanik´oj pa le kojkemchak:
+ Che´aq´atej taq tzukub´al (on chak´ak´arisaj taq wiqitajem chech uya´ik b´e chi kech taq tzukub´al)
+ Chayuju´ chech kichupik cookies chi´l utzukuxik chi´l unik´oxik b´antal kanoq
+
+
+%1$s are b´anowinaq le Mozilla. Le qachak are uya´ik jun utzalaj, ujaqik kemk´atz.
+Chaweta´maj nik´aj chik
]]>
+
+
+ Echeb\'alil chi\'l chajinem
+
+
+ Tzukunem, cookies, jalajoj ucha\'ik rech taq juq\'attzij
+
+
+ Uya\'ik pa ya\'om chi uloq, utz\'aqatisaxik pa utukelam
+
+
+
+
+ Chi rij %1$s, tob\'anem
+
+
+ Upam chak rech web\'
+
+
+ Uk\'exik taq kojkemchak
+
+
+ Chi ronojel
+
+
+ Nik’ob’al ya’om chi uloq, ch’ab’al
+
+
+ Mulib\'al rech juq\'attzij chi\'l ukojik
+
+ Utzukuxik
+
+
+ Chariqa’
+
+
+ %1$s kutaq b‘ik jachike katz‘ib‘aj pa kemwiqb‘al pa le ch‘ich‘ rech tzukunem
+
+
+ Ya\'om chi uloq
+
+
+ Ch’ich’ rech tzukunem
+
+
+ Utzijik
+
+
+ Uchupik
+
+
+ Utz\'aqatisaxik pa utukelam URL
+
+
+ Chech taq uwi´ wokk´olib´al web´
+
+
+ Chatzija´ are chi kutz´aqatisaj %s pa uwi´ 450 URLs le qas kkojik pa le q´e´tb´al rech keriqonib´al.
+
+
+ Chech taq uk‘olib‘al web‘ le xaya‘o
+
+
+ Chatz´ija´ are chi kutz´aqatisaj %s ajawatal taq URLs.
+
+
+ Chawilawachij taq uk‘olib‘al web‘
+
+
+ Chawilawachij taq uk‘olib‘al web‘
+
+
+ + Uya\'ik winaqirisam URL
+
+
+ Ucholajil utz‘aqatisaxik pa utukelam:
+
+
+ Chaya‘ URL
+
+
+ Uya\'ik winaqirisam URL
+
+
+ Uya\'ik winaqirisam URL
+
+
+ Chaya‘ kemwiqb‘al chech utz‘aqatisaxik
+
+
+ Cookies chi\'l taq juq\'attzij rech wokk\'olib\'al web\'
+
+
+ Jalajoj ucha\'ik rech juq\'attzij
+
+
+ Chawelesaj winaqirisam URLs
+
+
+ Chaweta\'maj nik\'aj chik
+
+
+ Chaya\' chi\'l chawilawachij winaqirisam utz\'aqatisaxik pa utukelam URLs.
+
+
+ URL chech uya\'ik
+
+
+ Chanak\'a\' on chatz\'ib\'aj URL
+
+
+ K\'utb\'al: mozilla.org
+
+
+ K\'utb\'al: example.com
+
+
+ K\'ak\' winaqirisam URL ya\'om.
+
+
+ Relesaxik
+
+
+ Relesaxik
+
+
+ Kamul chaq\'atuj le URL le xatz\'ib\'aj.
+
+ Ch\'ab\'al
+
+ Wokchakub\'al ya\'om chi uloq
+
+ Echeb\'alil
+ Q\'ateb\'al chi\'l taq tzukub\'al
+ Jujun taq ukemtzijob\'exik kkiterene\'j rij taq solinem pa taq wokk\'olib\'al web\', pine\' man kapitz\' ta pa taq ukemtzijob\'exik
+ Chaq\'atej taq k\'oxomab\'al rech taq tzukub\'al
+ Kkoj chech umulixik, uk\'oxomaxik, chi\'l retaxik taq b\'anowem pacha\' tz\'ib\'anem chi\'l silob\'em
+ Uq\'atexik taq tzukub\'al rech uk\'iyal
+ Wiqital pa taq wokk\'olib\'al web\' chech uterene\'xik taq solinem chi\'l chech uk\'utunsaxik taq chakunem pacha\' taq pitz\'b\'al rech ukomone\'xik
+ Chaq\'atej nik\'aj chi taq tzukub\'al upam chak
+ Le utzijik wene kub\'ano chi jujun taq uxaq wuj man eye\'tal ta le kkib\'ano
+ Chaq\'atej cookies
+
+
+ Ja‘i‘ tyox
+ Xew chaq‘atej le cookies rech terenem kech urox
+ "Xew chaq\'atej cookies kech 3urox "
+
+ Chaq‘atej cookies pa taq uk‘olib‘al web‘
+ Je‘ chab‘ana‘ jun toq‘ob‘
+
+
+ Chakojo‘ uwi‘ q‘ab‘aj chech utzoqopitajik kojkemchak
+
+
+ Chatzoqopij ruk‘ ukojik uwi‘q‘ab‘aj we akojom taq ramib‘al b‘ e we jaqatal chi le uk‘olib‘al web‘ pa %s.
+
+
+ Man q\'alaj taj
+
+ Rowaxik taq uxaq web\' are taq kk\'ex taq kojkemchak.
+
+ Chajinem
+
+ Chakunem
+ Uq\'atexik taq tz\'ib\' rech web\'
+
+ Wene kub\'ano chi ke\'etzaq taq retno\'jib\'al on taq wachib\'al
+
+ Uq\'atexik JavaScript
+
+ Le taq uxaq wuj wene aninaq uya\'ik uchuq\'ab\' , xa kut wene man eye\'tal ta le kkib\'ano
+
+
+ Chab\'ana\' %1$s nik\'ob\'al ya\'om chi uloq
+
+ Mozilla
+ Utaqik taq juq\'attzij rech ukojik
+
+
+ Chaweta\'maj nik\'aj chik
+
+
+ Le Mozilla kukoj uchuq\'ab\' chech umulixik xew le rajawaxik kya\'ik chi\'l uk\'exsuk\'umaxik %1$s chi kech konojel.
+
+
+ Ub\'ixikil rech echeb\'alil
+
+
+ Q‘alajisanem rech wujil
+
+
+ Taq k‘olwuj le kqakojo
+
+
+ %s OSS taq k‘olwuj
+
+
+ Chi rij %1$s
+
+
+ Jeqeb\'am taq ch\'ich\' rech tzukunem
+
+
+ Utzalijisaxik taq ch\'ich\' rech tzukunem ya\'om chi uloq\n
+
+
+ + Chaya\' jun chi ch\'ich\' rech tzukub\'al
+ Chawelesaj taq ch\'ich\' rech tzukub\'al
+ Relesaxik
+
+
+ Chaya‘ jun chik ch‘ich‘ rech tzukub‘al
+
+
+ Chacha‘ le ajawatal ch‘ich‘:
+
+
+ Chaya\' ch\'ich\' rech tzukub\'al
+
+ Chatzukuj ub\'i\' ch\'ich\'
+ Chatzukuj jucholaj tzij chech ukojik
+ Uk\'olik
+
+
+ K\'utub\'al: example.com/search/?q=%s
+
+ K\'ak\' ch\'ich\' rech tzukub\'al ya\'om.
+
+ Chatz\'ib\'aj ub\'i\' ch\'ich\' rech tzukub\'al
+ Jun jeqeb\'am ch\'ich\' rech tzukub\'al ktajin chi kukoj la\' le b\'i\'aj.
+
+ Chatz\'ib\'aj cholaj tzij rech tzukunem
+
+ Chaq\'atuj chi le cholaj tzij rech tzukunem kjunamataj ruk\' le k\'amwach rech k\'utub\'al
+
+
+ Q\'alaj okib\'al
+
+
+ Relesaxik
+
+
+ Chayuju\' le b\'anatal kanoq rech nik\'onem
+
+
+ Jaqatal taq k\'utjaqb\'al: %1$s
+
+
+ Chajital chajinem
+
+
+ Ktajin uya\'ik uchuq\'ab\'
+
+
+ Wokk\'olib\'al web\' ya\'om uchuq\'ab\'
+
+
+ Nik\'aj chi Jalajoj taq ucha\'ik
+
+
+ Pitz‘b‘al rech nik‘aj chik
+
+
+ Nik\'onem chi uwach
+
+
+ Uya\'ik chi jumul uchuq\'ab\' wokk\'olib\'al web\'
+
+
+ Nik\'onem chi rij
+
+
+ Uq\'atab\'axik uya\'ik uchuq\'ab\' uk\'olib\'al web\'
+
+
+ Tzalijem pa kojkemchak kanoq
+
+
+ Q‘atetal le ajilab‘al kech e terenelab‘
+
+
+ Uq\'atexik taq tzukub\'al
+
+ Taq aya\'talil
+
+ Chajaqa\' kemwiqb\'al pa jun chi kojkemchak
+
+ Utz kaya\' kan %1$s chech ujaqik we kemwiqb\'al pa %2$s.
+
+ Chariqa\' jun kemwiqb\'al le kkowinik kujaq kemwiqb\'al
+
+ Man k\'o ta jun chi kech taq kojkemchak pa le awiqkemchakub\'al kkowinik kujaq we kemwiqb\'al, utz kaya\' kan %1$s chech utzukuxik %2$s jun kojkemchak le kkowinik.
+
+ ¿Kawaj katel pa Nik\'ob\'al rech echeb\'alil?
+
+
+ %1$s k\'isom
+
+
+ Ujaqik
+
+
+
+
+
+
+
+
+
+
+ Ya‘om pa ramib‘al b‘e
+
+ Man kriqitaj ta le kempatanel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Chatz‘apij
+
+
+
+ Utz apetem pa %1$s
+
+
+ Aninaq. Echeb‘alil. Man k‘o ta kyujuw uwach.
+
+
+ Chamajij
+
+
+
+ %1$s man junam ta kuk‘ e nik‘aj chi nik‘onelab‘
+
+
+ Xqajosq‘ij le xajtajem are taq xatz‘apij le kojkemchak.
+
+
+
+ Chab‘ana‘ %1$s chech le ya‘om chi uloq chech uchajixik le ajuq‘attzij ruk‘ apachike kemwiqb‘al le kajaqo.
+
+
+ Chajeqeb‘a‘ pacha‘ nik‘onel ya‘om chi uloq
+
+
+ Relesaxik
+
+
+ Chaya\' uchuq\'ab\' le awecheb\'alil
+
+ Chak\'ama\' b\'i le nik\'ob\'al rech echeb\'alil pa le jun chi cholajil. Che\'aq\'atej taq ukemtzijob\'exik chi\'l jun chi upam chak le kkowinik ktzukun pa taq kemk\'olib\'al web\' chi\'l kuq\'atej taq ukanil uya\'ik uchuq\'ab\' uxaq wuj.
+
+
+ Le atzukunem, le a b\'e
+
+ ¿Ktajin katzukuj jalan taq jastaq? Chacha\' jun chi chi\'ch\' rech tzukunem ya\'om chi uloq pa taq suk\'manem.
+
+
+ Chaya\' taq ramb\'al b\'e pa le umajib\'al q\'axwach
+
+ Chattzalija pa taq ajawatal taq wokk\'olib\'al web\' pa %1$s aninaq. Xew chacha\' \"Uya\'ik pa umajib\'al q\'axwach\" pa le %1$s cholokib\'al.
+
+
+ Chab\'ana\' jun amaq\'el ub\'anik chech le echeb\'alil
+
+ Chaya\' %1$s pacha\' anik\'onel ya\'om chi uloq chi\'l chariqa\' le tob\'anem rech nik\'onem rech echeb\'alil are taq kajaq taq uxaq web\' pa nik\'aj chi taq kojkemchak.
+
+ Utz, ¡xink\'oxomaj!
+ Relesaxik
+ Le teren chi uloq
+
+
+ -
+
+
+ Uya\'ik
+
+
+ JE‘
+
+
+ Uq\'atexik
+
+
+ JA‘I‘
+
+
+ Chuputal chajib‘al rech kexsuk‘umatal chajib‘al rech uterne‘xik
+
+
+ Kemchak rech echeb\'alil tzukub\'al
+
+
+ Le taq Ub\'ixikil kuya\' b\'e kachup le %1$s kemchak ruk\' jun pitz\'onik. Man rajawaxik taj kajaq le kojkemchak on kawilo jas ktajin kub\'an le anik\'onel.
+
+
+ Uchupik b\'antal kanoq rech nik\'onel
+
+
+ Chaqasaj Firefox
+
+
+
+
+
+
+
+
+ Uwujil Mozilla kech uk‘iyal chi‘l nik‘aj chi jaqatal taq uwujil uxe‘al. ]]>
+
+
+ waral.]]>
+
+
+ taq uwujil.]]>
+
+
+ GNU Wujil kech uk‘iyal v3, xuquje‘ k‘o pa waral .]]>
+
+
+ Ub\'i\' rajaw
+ Retokib\'al
+ Uchupik
+
+
+
+ Chajital t\'iqonem
+ Man chajital ta t\'iqonem
+
+ Q\'atum rumal: %1$s
+
+
+ Chajital wokk\'olib\'al web\'
+ URL k\'o chik
+
+
+ Uriqik pa uxaq wuj
+
+
+ Uriqik pa uxaq wuj
+
+
+ %1$d/%2$d
+
+ %1$d man k\'o ta pa%2$d
+
+
+ Chariqa\' uwachinem teren chi uloq
+
+ Chariqa\' uwachinem kanoq
+
+ Man kriqitaj ta pa uxaq wuj
+
+
+
+
+ Tz\'onoj rech wokk\'olib\'al web\' rech ilwach chak
+
+
+ Uk‘olib‘al ilwach
+
+
+ Elesam uwach URL
+
+
+ Chakub‘al kech winaqirisanelab‘
+
+
+ Ujaqik taq kemwiqb‘al pa taq kemkojchak
+
+
+ Cholpaqalik
+
+
+ Taq ya‘b‘al b‘e rech k‘olib‘al
+
+
+ Ja\'i\'
+
+
+ Je\'
+
+
+ Relesaxik
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-quy/strings.xml b/mobile/android/focus-android/app/src/main/res/values-quy/strings.xml
new file mode 100644
index 0000000000..456e37c9da
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-quy/strings.xml
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+ Tatiy
+ ARI
+
+ Jallich\'ay
+
+ Mask\'ay mana chay dirección ñisqaman yaykuy
+
+ Sapanmanta asuy t\'aqwiriy. T\'aqwiriy. Pichay. Kutikipay.
+
+ Ñawpa yaykusqa t\'aqwiriyniyki picharpakun.
+
+ Mask\'ay kaypaq %1$s
+
+ Qunakuy…
+
+ Kitiqpata Ch\'ampanta Willay
+
+ Kichariy kaypi %1$s
+
+ Kichariy kaypi…
+
+ Qallariy pantallaman yapaykuy
+
+ Allinchaykuna
+ Kaymanta
+ Yanapay
+ Derechosniyki
+
+ Mask\'achiqkuna ch\'atasqa
+
+ Kallpachasqa kaywan %1$s
+
+ Via ñisqata qunakuy
+
+ Ñawpa yaykusqa t\'aqwiriyta pichay
+
+ Kichay
+
+ Pichaytaq kichariytaq
+
+ Pichay
+
+ Ñawpa yaykusqa t\'aqwiriyta pichay
+
+
+ Asuy chanta Takya
+
+ Tarichiqkunamanta, cookies kaqkunamanta, willaykunamanta ima akllaykuna
+
+ Akllasqañajinata churay, sapanmanta junt\'achiy
+
+ Kaymanta %1$s, yanapay
+
+ Web kaqmanta Tiyapuynin
+
+ Apps kaqkunata tikraychaspa
+
+ Jatunnin
+
+ Willaykunamanta tantaynin chanta llamk\'aynin ima
+
+ Mask\'ay
+
+ Ajinaña jamuq
+
+ Jap\'ichisqa
+
+ Wañuchisqa
+
+ URL sapanmanta junt\'achiy
+
+ + sapanchaykunata yapay URL
+
+ Sapanchaykunata yapay URL
+
+ Sapanchaykunata yapay URL
+
+ Cookies chanta Kitimanta Willaykuna
+
+ Willaykunamanta Akllaykuna
+
+ Sapanchasqa URLs raqpay
+
+ Astawan yachay
+
+ Yapay chanta kamachiy sapanchasqa sapanmanta junt\'achiyta URLs.
+
+ URL yapanapaq
+
+ Mach\'ay mana chay URL qillqay
+
+ Kayjina: mozilla.org
+
+ Kayjina: example.com
+
+ Musuq sapanchasqa URL yapasqa.
+
+ Raqpay
+
+ Raqpay
+
+ Watiqmanta qhawaykuy kay URL qillqasqaykita.
+
+ Runasimi
+ Sistemaqpata ajinaña jamuynin
+
+ Asuy
+ Publicidadmanta tarichiqkunata ch\'ataykuy
+ Wakin willaykuna, manapis qam paykunaman yaykuykuqtiykipis, yaykusqaykita kay web raphikunaman tarinku
+ Analiticos tarichiqkunata ch\'ataykuy
+ Kaywan llamk\'akun ruwaykunaykita tantanapaq, qhawaykachanapaq chanta tupunapaq ima, kikinta imaynatachus jukta akllaptiyki mana chay raphinta suchuptiykijina
+ Social tarichiqkunata ch\'ataykuy
+ Raphikunapi yapasqa kachkanku yaykuyniykita tarinankupaq chanta llamk\'ayta rikuchinanpaq, kikin botonesjina qunakunapaq
+ Wak tiyapuykunamanta tarichiqkunata ch\'ataykuy
+ Kawsachiyqa wakin raphikuna mana suyaq llamk\'achiy kanankuta ruwachinman
+ Cookies ch\'ataykuy
+
+ Cookies 3 ñiqi kaqkunallaqpata ch\'ataykuy
+
+ Mana rikukuq
+ Web raphikunata pakay ruwaykunata chhalaptiyki
+
+ Llamk\'ay thaskiynin
+ Web paqariykunata ch\'ataykuy
+ Unanchakunata mana chay rikch\'aykunata pisichiy atinman
+
+ JavaScript ch\'ataykuy
+ Raphikuna aswan utqhayta chaqnanay atinkuman, ichaqa mana suyaq kaqjinata llamk\'ay atillankutaq
+
+ Tikray %1$s ajinaña churasqa t\'aqwiriqniykipi
+
+ Mozilla
+ Llamk\'akusqamanta willaykunata kachay
+
+ Astawan yachay
+
+ Mozilla aswan munasqallata tantananpaq kaypachakun qusunankupaq chanta allinchanankupaq %1$s tukuypaq.
+
+ Asuymanta willay
+
+ Kaymanta %1$s
+
+ Mask\'aq motores chantisqa
+
+ Ajinaña jamuq mask\'aq motoresta jamuyninman kutichiy
+
+ + Wak mask\'aq motorta yapay
+ Mask\'aq motoresta raqpay
+ Raqpay
+
+ Mask\'aq motorta yapay
+
+ Mask\'aq motorpata sutin
+ Mayqin mask\'aq wallqawanchus llamk\'akunqa
+ Jallich\'ay
+
+ Kayjina: example.com/search/?q=%s
+
+ Musuq mask\'aq motor yapasqa.
+
+ Mask\'aq motor sutinta qillqay
+ Juk chantisqa mask\'aq motor chay sutiwan llamk\'achkanña.
+
+ Mask\'aq wallqata qillqay
+
+ Qhawaykachay mask\'aq wallqaqa kikin kananta ejemplo rikukuywan
+
+ Registrota pichay
+
+ Wich\'uy
+
+ Ñawpa yaykusqa t\'aqwiriyta pichay
+
+ Tikllakuna kichasqa: %1$s
+
+ Takya t\'inkiy
+
+ Chaqnanachkan
+
+ Web raphi chaqnanasqa
+
+ Aswan akllaykuna
+
+ Ñawpaqman riy
+
+ Watiqmanta chaqnanay web raphita
+
+ Ñawpa raphiman riy
+
+ Kay raphiqpata chaqnanayninta sayay
+
+ Ñawpa aplicacionman kutiy
+
+ Tarichiqkunata ch\'ataykuy
+
+ Derechosniyki
+
+ T\'inkiyta kichariy wak aplicaciónwan
+ Lluqsiy atiwaq kaymanta %1$s kay t\'inkiyta kaypi %2$s kichanaykipaq.
+ Juk aplicación tariy mayqinchus kay t\'inkiyta kichariy atinanpaq
+ Ni mayqin aplicaciones kay dispositivoykiqpata kay t\'inkiyta kichariy atinchu. Lluqsiy atiwaq kaymanta %1$s mask\'anaykipaq kaypi %2$s juk tupakuq aplicacionta.
+ ¿Asuy t\'aqwiriymanta lluqsinkichu?
+
+ %1$s tukusqa
+
+ Kichay
+
+
+
+
+
+
+
+
+
+ Mana jaywaqta tarikunchu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Asuykita kallpachay
+ Qhipan ñiqiman kay asuy t\'aqwiriyta apay. Willaykunata chanta wak tiyapuykunata ima ch\'ataykuy mayqinkunachus tarisuy atinkuman kitinninta chanta raphikunaqpata chaqnanayninta jayrayan ima.
+
+ Mask\'ayniyki, munasqaykimanjina
+ ¿Jukjinatachu mask\'achkanki? Wak ajinaña jamuq mask\'aq motorta akllay configuracionpi.
+
+ Qallariy pantallaykiman chiqan yaykunata yapay
+ Aswan munasqa kitikunaykita watiqmanta watukuy kaypi %1$s utqhaytapacha. Akllay \"Qallariy pantallaman yapay\" kay %1$s menu kaqpi.
+
+ Asuymanta juk sapakuti ruwayta ruway
+ Churaykuy kayta %1$s ajinaña jamuq t\'aqwiriqjinata chanta asuy t\'aqwiriy allin jamuyninta qhawaykachay ima kay web raphikunata kicharispa wak aplicacionesmantapacha.
+
+ ¡Ari, jap\'iqasqa!
+ Phinkiy
+ Qhipan
+
+ -
+
+ Yapay
+
+ Tatiy
+
+ Asuy t\'aqwiriymanta sesión
+
+ Willaykuqkuna sesion kaqta pichay atichisunki kaymanta %1$s juk ñit\'iywan. Mana aplicacionta kicharinallaykiñachu tiyan nitaq qhawayta imachus t\'aqwiriqpi llamk\'akuchkanta.
+
+ Ñawpa yaykusqa t\'aqwiriyta pichay
+
+ Firefox kaqta uraykuchiy
+
+ %1$s kaqqa juk software kichasqa codigoyuqtaq chanta qhasitaq, Mozillawan chanta wak yanapaqkunawan ima paqarichisqa.
+
+
+
+
+ Yaykuq suti
+ Pakasqa willay
+ Pichay
+
+ Takya t\'inkiy
+ Mana takya t\'inkiy
+ Qhawaychasqa kaywan: %1$s
+
+ Kitimanta takya
+ Kay URL kanña
+
+ Kay raphipi tariy
+
+
+
+ Escritoriomanta kitita mañay
+
+ URL kikinchasqa
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ro/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000000..b0387686ee
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ro/strings.xml
@@ -0,0 +1,661 @@
+
+
+
+
+
+
+
+
+ Renunță
+
+ OK
+
+ Salvează
+
+
+ Caută sau introdu adresa
+
+ Navigare privată automată.\nNavighează. Șterge. Repetă.
+
+
+ Istoricul de navigare a fost șters.
+
+
+ Istoricul de navigare al filei a fost șters.
+
+
+ Caută %1$s
+
+
+ Partajează…
+
+
+ Raportează problemă cu site-ul
+
+
+ Deschide în %1$s
+
+
+ Deschide în…
+
+
+ Adaugă la ecranul principal
+
+ Setări
+ Despre
+ Ajutor
+ Drepturile tale
+
+
+ Elemente de urmărire blocate
+
+
+ Dezactivarea poate remedia unele probleme legate de site
+
+
+ Blocare de conținut
+
+ Dezactivează pentru a repara unele site-uri
+
+
+ Susținut de %1$s
+
+
+ Partajează via
+
+
+ Șterge istoricul de navigare
+
+
+ Deschide
+
+
+ Șterge și deschide
+
+
+ Șterge
+
+
+ Șterge istoricul de navigare
+
+
+
+ Șterge și deschide
+
+
+ Șterge și deschide %1$s
+
+
+
+ %1$s îți oferă controlul.
+Folosește-l ca browser privat:
+
+ Caută și navighează direct în aplicație
+ Blochează elementele de urmărire (sau actualizează setările pentru a permite elementele de urmărire)
+ Șterge pentru a elimina cookie-urile, precum și istoricul de căutare și navigare
+
+
+%1$s este produs de Mozilla. Misiunea noastră este să promovăm un internet sănătos și deschis.
+Află mai multe
]]>
+
+
+ Confidențialitate și securitate
+
+
+ Urmărire, cookie-uri, împărtășire de date
+
+
+ Setează motorul implicit, completarea automată
+
+
+
+
+ Despre %1$s, ajutor
+
+
+ Conținut web
+
+
+ Comutare de aplicații
+
+
+ General
+
+
+ Colectare de date și utilizare
+
+ Căutare
+
+
+ Obține sugestii de căutare
+
+ %1$s va trimite ce tastezi în bara de adresă la motorul de căutare
+
+
+ Implicit
+
+
+ Motor de căutare
+
+
+ Activat
+
+
+ Dezactivat
+
+
+ Completare automată de URL-uri
+
+
+ Pentru site-uri de top
+
+
+ Pentru site-uri pe care le adaugi
+
+
+ Gestionează site-urile
+
+
+ Gestionează site-urile
+
+
+ + Adaugă URL personalizat
+
+
+ Adaugă URL personalizat
+
+
+ Adaugă URL personalizat
+
+
+ Adaugă un link pentru completare automată
+
+
+ Cookie-uri și date ale site-urilor
+
+
+ Împărtășire de date
+
+
+ Elimină URL-uri personalizate
+
+
+ Află mai multe
+
+
+ Adaugă și gestionează URL-uri autocompletate personalizate.
+
+
+ URL de adăugat
+
+
+ Lipește sau introdu un URL
+
+
+ Exemplu: mozilla.org
+
+
+ Exemplu: example.com
+
+
+ Nou URL personalizat adăugat.
+
+
+ Elimină
+
+
+ Elimină
+
+
+ Verifică URL-ul introdus.
+
+ Limbă
+
+ Setarea implicită a sistemului
+
+ Confidențialitate
+ Blochează elementele de urmărire publicitare
+ Anumite reclame urmăresc vizitele pe site, chiar dacă nu dai clic pe reclame
+ Blochează elementele de urmărire analitice
+ Folosite pentru a colecta, a analiza și a măsura activități precum atingerea și derularea
+ Blochează elementele de urmărire ale rețelelor sociale
+ Încorporate în site-uri pentru a-ți urmări vizitele și a afișa funcții precum butoanele de distribuire
+ Blochează alte elemente de urmărire de conținut
+ Activarea poate cauza un comportament neașteptat al unor pagini
+ Blochează cookie-urile
+
+ Blochează numai cookie-urile de urmărire de la terți
+ Blochează numai cookie-urile de la terți
+
+ Blochează cookie-urile inter-site-uri
+
+
+ Folosește amprenta pentru a debloca aplicația
+
+
+ Discret
+
+ Ascunde paginile web la comutarea între aplicații și blochează realizarea capturilor de ecran.
+
+ Securitate
+
+ Performanță
+ Blochează fonturile web
+
+ Poate duce la pictograme și imagini lipsă
+
+ Blochează JavaScript
+
+ Paginile se pot încărca mai rapid, dar este posibil și să aibă un comportament ciudat
+
+
+ Desemnează %1$s ca browser implicit
+
+ Mozilla
+ Trimite date privind utilizarea
+
+
+ Află mai multe
+
+
+ Mozilla se străduiește să colecteze doar strictul necesar pentru a furniza și a îmbunătăți %1$s pentru toți.
+
+
+ Politica de confidențialitate
+
+
+ Despre %1$s
+
+
+ Motoare de căutare instalate
+
+
+ Restaurează motoarele de căutare implicite
+
+
+ + Adaugă alt motor de căutare
+ Elimină motoare de căutare
+ Elimină
+
+
+ Adaugă motorul de căutare
+
+ Numele motorului de căutare
+ Text de căutare spre utilizare
+ Salvează
+
+
+ Exemplu: example.com/search/?q=%s
+
+ Motor nou de căutare adăugat.
+
+ Introdu numele motorului de căutare
+ Un motor de căutare instalat folosește deja acest nume.
+
+ Introdu șirul de căutare
+
+ Verifică dacă șirul de căutare se potrivește cu formatul exemplu
+
+
+ Șterge adresa introdusă
+
+
+ Înlătură
+
+
+ Șterge istoricul de navigare
+
+
+ File deschise: %1$s
+
+
+ Conexiune securizată
+
+
+ Se încarcă
+
+
+ Site web încărcat
+
+
+ Mai multe opțiuni
+
+
+ Buton pentru mai multe opțiuni
+
+
+ Navighează înainte
+
+
+ Reîncarcă site-ul web
+
+
+ Navighează înapoi
+
+
+ Oprește încărcarea site-ului web
+
+
+ Întoarce-te la aplicația anterioară
+
+
+ Numărul de elemente de urmărire blocate
+
+
+ Blochează elementele de urmărire
+
+ Drepturile tale
+
+ Deschide linkul în altă aplicație
+
+ Poți părăsi %1$s pentru a deschide acest link în %2$s.
+
+ Găsește o aplicație care poate deschide linkul
+
+ Niciuna dintre aplicațiile de pe dispozitiv nu poate deschide acest link. Poți părăsi %1$s ca să cauți în %2$s o aplicație care poate.
+
+ Ieși din navigarea privată?
+
+
+ Descărcarea %1$s s-a terminat
+
+
+ Deschide
+
+
+
+
+
+
+
+
+
+ Server negăsit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Consolidează-ți confidențialitatea
+
+ Du navigarea privată la nivelul următor. Blochează reclamele și alte tipuri de conținut care te pot urmări pe site-uri și care îngreunează timpii de încărcare.
+
+
+ Caută în felul tău
+
+ Cauți ceva diferit? Alege un alt motor de căutare implicit din Setări.
+
+
+ Adaugă scurtături la ecranul principal
+
+ Întoarce-te numaidecât la site-urile tale favorite în %1$s. Selectează pur și simplu „Adaugă la ecranul principal” din meniul %1$s.
+
+
+ Obișnuiește-te cu confidențialitatea
+
+ Setează %1$s ca browser implicit și beneficiază de avantajele navigării private atunci când deschizi pagini web din alte aplicații.
+
+ OK, am înțeles!
+ Omite
+ Înainte
+
+
+ -
+
+
+ Adaugă
+
+
+ Renunță
+
+
+ Sesiune de navigare privată
+
+
+ Notificările îți permit să ștergi sesiunea %1$s cu o atingere. Nu e nevoie să deschizi aplicația sau să vezi ce rulează în browser.
+
+
+ Șterge istoricul de navigare
+
+
+ Descarcă Firefox
+
+
+
+
+
+
+
+
+ Utilizator
+ Parolă
+ Curăță
+
+
+
+ Conexiune securizată
+ Conexiune nesigură
+
+ Verificat de: %1$s
+
+
+ Securitatea site-ului
+ URL-ul există deja
+
+
+ Caută în pagină
+
+
+ Caută în pagină
+
+
+ %1$d / %2$d
+
+ %1$d din %2$d
+
+
+ Caută următorul rezultat
+
+ Caută rezultatul anterior
+
+ Anulează căutarea în pagină
+
+
+
+
+ Cere versiune de desktop
+
+
+ URL copiat
+
+
+ Unelte dezvoltator
+
+
+ Avansat
+
+
+ Depanare de la distanță prin USB/Wi-Fi
+
+
+ Deschide linkul într-o nouă sesiune
+
+
+ Pictogramă amprentă
+
+
+ Amprentă nerecunoscută. Încearcă din nou.
+
+
+ Deget mutat prea repede. Încearcă din nou.
+
+
+ Nu
+
+
+ Da
+
+
+ Unele motoare de căutare nu pot afișa sugestii.
+
+
+ Înlătură
+
+
+
+
+ Site-ul are un comportament neașteptat?\n Încearcă să dezactivezi protecția împotriva urmăririi
+
+
+ Adaugă la ecranul principal]]>
+
+
+ Deschide fiecare link în %1$s\n Setează %1$s ca browser implicit
+
+
+ Completează automat URL-urile site-urilor pe care intri des\n Apasă lung pe orice URL din bara de adresă
+
+
+ Deschide un link într-o filă nouă\n Apasă lung pe orice link dintr-o pagină
+
+
+ Dezactivează ponturile din ecranul de start
+
+
+ Filă nouă deschisă
+
+
+ Comută
+
+
+ Comută imediat pe link într-o filă nouă
+
+
+ Blochează site-urile potențial periculoase și înșelătoare
+
+ Blochează site-urile raportate ca fiind înșelătoare și de atac, cu programe rău intenționate și programe nedorite.
+
+
+ Excepții
+
+ Ai dezactivat blocarea de conținut pe aceste site-uri web.
+
+ Elimină
+
+ Elimină toate site-urile web
+
+
+ Blochează cookie-uri
+
+
+ Dorești să blochezi cookie-urile?
+
+
+ Filă închisă neașteptat
+
+ Ne pare rău. Avem o problemă cu această filă.
+
+ Ca browser privat, nu salvăm niciodată și nu putem restaura această filă.
+
+ Închide fila
+
+
+
+
+
+ Trimite un raport de defecțiune către Mozilla
+
+
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ru/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000000..3bc7cd998e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,1122 @@
+
+
+
+
+
+
+
+
+ Отмена
+
+ ОК
+
+ Сохранить
+
+
+ Введите запрос или адрес
+
+ Автоматический режим приватного просмотра.\nПросмотреть. Стереть. Повторить.
+
+
+ Ваша история посещения сайтов была стёрта.
+
+ История просмотра сети удалена
+
+
+ История просмотров вкладки была стёрта.
+
+
+ Поиск %1$s
+
+
+ Поделиться…
+
+
+ Сообщить о проблеме с сайтом
+
+
+ Открыть в %1$s
+
+
+ Открыть в…
+
+
+ Добавить на домашний экран
+
+
+ Добавить в ярлыки
+
+ Удалить из ярлыков
+
+
+ Настройки
+ О приложении
+ Справка
+ Ваши права
+
+
+ Трекеров заблокировано
+
+
+ Отключение этой функции может решить некоторые проблемы с сайтом
+
+
+ Блокировка содержимого
+
+ Отключить для решения проблем с некоторыми сайтами
+
+
+ На движке %1$s
+
+
+ Поделиться через
+
+ Удалить историю просмотра?
+
+ Нажмите или снимите это уведомление, чтобы надежно удалить свою историю просмотра.
+
+
+ Нажмите или снимите это уведомление, чтобы надежно удалить свою историю просмотра.
+
+ Удалить историю просмотра сети
+
+
+ Открыть
+
+
+ Удалить и открыть
+
+
+ Удалить
+
+
+ Удалить историю просмотра сети
+
+
+
+ Стереть и открыть
+
+
+ Удалить и открыть %1$s
+
+
+
+ Искать в Focus
+
+ Искать в Klar
+
+ Искать в Focus Beta
+
+ Искать в Focus Nightly
+
+
+ %1$s даёт вам контроль.
+Используйте его как приватный браузер:
+
+ Ищите и просматривайте сеть
+ Блокируйте трекеры (или измените настройки, чтобы разрешить их)
+ Стирайте и удаляйте куки, историю поиска и просмотра
+
+
+%1$s создан Mozilla. Наша миссия способствовать здоровому, открытому Интернету.
+Узнать больше
]]>
+
+
+ Приватность и защита
+
+
+ Отслеживание, куки, выбор данных
+
+
+ Установка по умолчанию, автодополнение
+
+
+
+
+ О %1$s, помощь
+
+
+ Улучшенная защита от отслеживания
+
+
+ Сетевое содержимое
+
+
+ Смена приложений
+
+
+ Основные
+
+
+ Браузер по умолчанию, язык
+
+
+ Сбор и использование данных
+
+ Поиск
+
+
+ Получать поисковые предложения
+
+ %1$s будет отправлять поисковой системе текст, который вы вводите в адресной строке
+
+
+ По умолчанию
+
+
+ Поисковая система
+
+
+ Включено
+
+
+ Отключено
+
+
+ URL-адрес автодополнения
+
+
+ Для топа сайтов
+
+
+ Включить автодополнение в %s для более чем 450 популярных сайтов в адресной строке.
+
+
+ Для добавленных вами сайтов
+
+
+ Включить автодополнение %s URL ваших любимых сайтов.
+
+
+ Управление сайтами
+
+
+ Управление сайтами
+
+
+ + Добавить пользовательский URL
+
+
+ Ваш список автодополнения:
+
+
+ Добавить URL
+
+
+ Добавить пользовательский URL
+
+
+ Добавить пользовательский URL
+
+
+ Добавить ссылку для автодополнения
+
+
+ Куки и данные сайтов
+
+
+ Выбор данных
+
+
+ Удалить пользовательские URL
+
+
+ Подробнее
+
+
+ Добавление и управление пользовательскими URL автодополнения.
+
+
+ URL для добавления
+
+
+ Вставьте или введите URL-адрес
+
+
+ Пример: mozilla.org
+
+
+ Пример: example.com
+
+
+ Новый пользовательский URL добавлен.
+
+
+ Удалить
+
+
+ Удалить
+
+
+ Внимательно проверьте введённый URL.
+
+ Язык
+
+ Язык устройства
+
+ Приватность
+ Блокировать трекеры рекламы
+ Некоторая реклама отслеживает посещения сайтов, даже если вы на неё не нажимали
+ Блокировать трекеры аналитики
+ Используются для сбора, анализа и измерения активности, например, нажатий и скроллинга
+ Блокировать социальные трекеры
+ Вставляются на сайты, чтобы отслеживать ваши посещения и внедрять дополнительную функциональность, например, кнопку «Поделиться»
+ Блокировать другие трекеры содержимого
+ Включение этой функции может вызвать непредсказуемое поведение некоторых страниц
+ Не принимать куки
+
+
+ Нет, спасибо
+ Не принимать только куки от сторонних трекеров
+ Не принимать только куки со сторонних веб-сайтов
+
+ Блокировать межсайтовые куки
+ Да, пожалуйста
+
+
+ Использовать отпечатки пальцев для разблокировки приложения
+
+
+ Разблокировать с помощью отпечатка пальца, если вы добавили ярлыки или когда веб-сайт уже открыт в %s.
+
+
+ Невидимка
+
+ Скрывать веб-страницы при переключении приложений и блокировать создание скриншотов.
+
+ Защита
+
+ Производительность
+ Блокировать веб-шрифты
+
+ Может привести к отсутствию значков или изображений
+
+ Блокировать JavaScript
+
+ Страницы могут загружаться быстрее, но также могут вести себя неожиданно
+
+
+ Сделать %1$s браузером по умолчанию
+
+ Mozilla
+ Отправлять данные об использовании
+
+
+ Подробнее
+
+
+ Mozilla обязуется собирать только ту информацию, которая необходима для работы %1$s и его улучшения в будущем.
+
+
+ Политика приватности
+
+
+ Сведения о лицензии
+
+
+ Используемые библиотеки
+
+
+ %s | Свободные библиотеки
+
+
+ О %1$s
+
+
+ Установленные поисковые системы
+
+
+ Выберите поисковую систему
+
+
+ Восстановить поисковые системы
+
+
+ + Добавить другую поисковую систему
+ Удалить поисковые системы
+ Удалить
+
+
+ Добавить другую поисковую систему
+
+ Выберите свою поисковую систему:
+
+
+ Добавить поисковую систему
+
+ Имя поисковой системы
+ Строка поискового запроса
+ Сохранить
+
+
+ Пример: example.com/search/?q=%s
+
+ Добавлена новая поисковая система.
+
+ Введите имя поисковой системы
+ Установленная поисковая система уже использует это имя.
+
+ Введите поисковую строку
+
+ Убедитесь, что поисковая строка соответствует формату примера
+
+
+ Очистить ввод
+
+
+ Пропустить
+
+
+ Удалить историю веб-сёрфинга
+
+
+ Открытых вкладок: %1$s
+
+
+ Защищённое соединение
+
+
+ Загрузка
+
+
+ Сайт загружен
+
+
+ Дополнительно
+
+
+ Кнопка дополнительных настроек
+
+
+ Вперёд
+
+
+ Перезагрузить сайт
+
+
+ Перейти назад
+
+
+ Остановить загрузку сайта
+
+
+ Вернуться к последнему приложению
+
+
+ Трекеров заблокировано
+
+
+ Блокировать трекеры
+
+ Ваши права
+
+ Открыть ссылку в другом приложении
+
+ Вы можете выйти из %1$s, чтобы открыть эту ссылку в %2$s.
+
+ Найдите приложение, которое может открыть эту ссылку
+
+ Ни одно из приложений на вашем устройстве не может открыть эту ссылку. Вы можете выйти из %1$s для поиска подходящего приложения в %2$s.
+
+ Выйти из приватного просмотра?
+
+
+ %1$s загружено
+
+
+ Открыть
+
+
+
+
+
+
+
+
+
+
+ Добавлено в ярлыки!
+
+ Сервер не найден
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Закрыть
+
+
+
+ Добро пожаловать в %1$s
+
+
+ Быстро. Приватный. Никаких отвлечений.
+
+
+ Начать
+
+
+
+ %1$s не похож на другие браузеры
+
+
+ Мы удаляем вашу историю, когда вы закрываете приложение, для повышения приватности.
+
+
+
+ Используйте %1$s по умолчанию, чтобы защитить свои данные при открытии каждой ссылки.
+
+
+ Установить браузером по умолчанию
+
+
+ Пропустить
+
+
+
+ Усильте свою приватность
+
+ Поднимите приватный просмотр на новый уровень. Блокируйте рекламу и другое содержимое, которое может отслеживать вас и замедлять загрузку страниц.
+
+
+ Ваш поиск, Ваш Интернет
+
+ Искали что-то другое? Выберите другую поисковую систему по умолчанию в настройках.
+
+
+ Добавьте ярлыки на домашний экран
+
+ С %1$s вы сможете быстро вернуться к любимым сайтам. Просто выберите «Добавить на домашний экран» из меню %1$s.
+
+
+ Сделайте приватность своей привычкой
+
+ Установите %1$s своим браузером по умолчанию и получите преимущества приватного веб-сёрфинга, когда вы открываете веб-страницы из других приложений.
+
+ ОК, понятно!
+ Пропустить
+ Далее
+
+
+ -
+
+
+ Добавить
+
+
+ ДА
+
+
+ Отмена
+
+
+ НЕТ
+
+
+ Ссылка будет открываться без улучшенной защиты от отслеживания
+
+
+ Приватный сеанс
+
+
+ Уведомления позволяют стереть ваш сеанс в %1$s одним касанием. Вам не нужно открывать приложение или видеть, что запущено в вашем браузере.
+
+
+ Удалить историю просмотра сети
+
+
+ Загрузить Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License и других лицензий для открытого исходного кода.]]>
+
+
+ здесь.]]>
+
+
+ лицензиями.]]>
+
+
+ Основной общественной лицензии GNU 3-ей версии, доступные здесь .]]>
+
+
+ Имя пользователя
+ Пароль
+ Очистить
+
+
+
+ Защищённое соединение
+ Незащищённое соединение
+
+ Подтверждено: %1$s
+
+
+ Защита сайта
+ URL уже существует
+
+
+ Найти на странице
+
+
+ Найти на странице
+
+
+ %1$d/%2$d
+
+ %1$d из %2$d
+
+
+ Найти следующее совпадение
+
+ Найти предыдущее совпадение
+
+ Убрать поиск на странице
+
+
+
+
+ Запросить полную версию сайта
+
+
+ Версия для ПК
+
+
+ Ссылка скопирована
+
+
+ Инструменты разработчика
+
+
+ Открывать ссылки в приложениях
+
+
+ Дополнительно
+
+
+ Разрешения для сайтов
+
+
+ Снижение числа уведомлений о куках
+
+
+ Включено
+
+
+ Отключено
+
+
+ Снижение числа уведомлений о куках
+
+
+ Уменьшите число уведомлений, по возможности автоматически отклоняя запросы на использование кук.
+
+ -->
+ Снижение числа уведомлений о куках
+
+
+ ВКЛЮЧЕНО для этого сайта
+
+
+ В настоящее время сайт не поддерживается
+
+
+ ОТКЛЮЧЕНО для этого сайта
+
+
+ Снижение числа уведомлений о куках
+
+
+ ОТКЛЮЧЕНО для этого сайта
+
+
+ ВКЛЮЧЕНО для этого сайта
+
+
+ Включить Снижение числа уведомлений о куках для %1$s?
+
+
+ Отключить Снижение числа уведомлений о куках для %1$s?
+
+
+ %1$s удалит куки этого сайта и обновит страницу. Удаление всех кук может привести к выходу из системы или к пропаже покупок из корзины.
+
+
+ %1$s может попытаться автоматически отклонить запросы на использование кук.
+
+
+ Этот сайт в настоящее время не поддерживается функцией Уменьшения числа уведомлений о куках. Вы хотите, чтобы наша команда проверила этот веб-сайт и добавила поддержку в будущем?
+
+
+ Отмена
+
+
+ Запросить поддержку
+
+
+ Запрос на поддержку сайта отправлен.
+
+
+ Запрос на поддержку сайта отправлен.
+
+
+
+ %1$s пытается отклонить запросы кук, чтобы убрать надоедливые уведомления о куках.\n\nУправляйте настройками уведомлений о куках в %2$s.
+
+
+ настройках
+
+
+ Автовоспроизведение
+
+
+ Чтобы разрешить это:
+
+
+ 1. Перейдите в настройки Android
+
+
+ Разрешения]]>
+
+
+ Перейти в Настройки
+
+
+ %1$s в состояние РАЗРЕШЕНО]]>
+
+
+ Камера
+
+
+ Микрофон
+
+
+ Местоположение
+
+
+ Уведомления
+
+
+ Защищённое авторским правом содержимое
+
+
+ Всегда спрашивать
+
+
+ Блокировать
+
+
+ Разрешить
+
+
+ Заблокировано Android
+
+
+ Разрешить аудио и видео
+
+
+ Блокировать только звук
+
+
+ Желательно
+
+
+ Блокировать звуки и видео
+
+
+ Исследования
+
+
+ Firefox может время от времени устанавливать и проводить исследования.
+
+
+ Подробнее
+
+
+ Приложение закроется, чтобы применить изменения
+
+
+ Удалить
+
+
+ Активно
+
+
+ Завершено
+
+
+ Удалённая отладка через USB/Wi-Fi
+
+
+ Разблокировать
+
+
+ Подтвердите с помощью отпечатка пальца
+
+
+ Вы можете использовать свой отпечаток пальца, чтобы продолжить текущий сеанс работы с приложением.
+
+
+ Открыть ссылку в новом сеансе
+
+
+ Значок отпечатков пальцев
+
+
+ Отпечатки пальцев не распознаны. Попробуйте снова.
+
+
+ Слишком быстрое прикосновение. Попробуйте снова.
+
+
+ Показывать поисковые предложения?
+
+
+ Для получения предложений %1$s необходимо отправлять то, что вы вводите в адресной строке, в поисковую систему.
+
+
+ Нет
+
+
+ Да
+
+
+ Некоторые поисковые системы не могут показывать предложения.
+
+
+ Закрыть
+
+
+
+
+ Сайт ведёт себя неожиданно?\n Попробуйте отключить защиту от отслеживания
+
+
+ Добавить на домашний экран]]>
+
+
+ Открывайте все ссылки в %1$s\n Установить %1$s браузером по умолчанию
+
+
+ Автодополнение URL для часто посещаемых сайтов\n Произведите долгое нажатие на любой URL в адресной строке
+
+
+ Открывайте ссылки в новых вкладках\n Произведите долгое нажатие на любую ссылку на странице
+
+
+ Отключить советы на домашнем экране
+
+
+ Открыта новая вкладка
+
+
+ Перейти
+
+
+ Вход в полноэкранный режим
+
+
+ Сразу переходить на новую вкладку
+
+
+ Блокировать потенциально опасные и обманывающие сайты
+
+ Блокировать известные обманывающие и вредоносные сайты, заражённые сайты и сайты с нежелательным ПО.
+
+
+ Режим «Только HTTPS»
+
+
+ Автоматически пытаться подключиться к сайтам через протокол шифрования HTTPS для повышения безопасности.
+
+
+ Исключения
+
+ Вы отключили блокировку содержимого для этих веб-сайтов.
+
+ Удалить
+
+ Удалить все веб-сайты
+
+
+ Блокировать куки
+
+
+ Хотите заблокировать куки?
+
+
+ Вкладка упала
+
+ Извините. С этой вкладкой возникли проблемы.
+
+ Как приватный браузер, мы никогда не сохраняем и не можем восстановить эту вкладку.
+
+ Закрыть вкладку
+
+
+
+
+
+ Отправить сообщение о падении в Mozilla
+
+
+
+
+ Заблокировано трекеров, начиная с %s
+
+ Содержимое
+
+ Реклама
+
+ Соцсети
+
+ Аналитика
+
+ Улучшенная защита от отслеживания
+
+ Защита для этого сайта отключена
+
+ Защита для этого сайта включена
+
+ Соединение защищено
+
+ Соединение не защищено
+
+ Блокируемые трекеры и скрипты
+
+
+ Вернуться назад
+
+
+
+ Удалить
+
+
+ Переименовать
+
+ Переименовать
+
+
+ Название ярлыка
+
+
+ Сохранённые и отправленные изображения <b>не будут</b> удалены, когда вы сотрёте историю %1$s
+
+
+
+ Тема
+
+ Светлая
+
+ Тёмная
+
+ Учитывать режим экономии батареи
+
+ Использовать тему устройства
+
+
+
+ Этот сайт не поддерживает HTTPS
+
+
+ Подробнее
+ Измените эту настройку в меню «Настройки» > «Приватность и защита» > «Защита».]]>
+
+
+ Незащищённое соединение
+
+
+
+ Если в прошлом вы успешно соединялись с этим сервером, то, возможно, эта ошибка является временной.
+ ]]>
+
+
+ Возможно, кто-то пытается подменить нужный вам сайт другим, поэтому продолжение может быть рискованным.
+
+ %1$s не может доверять %2$s , потому что издатель его сертификата неизвестен, сертификат подписан самим сайтом или его сервер не отправляет правильные промежуточные сертификаты.
+ ]]>
+
+
+
+ Закрыть вкладку
+
+
+
+ Попались! Мы остановили слежку этого сайта за вами. Коснитесь щита всякий раз, чтобы узнать, что мы блокируем.
+
+
+ Закрыть всплывающее окно
+
+
+
+ Вы защищены!
+
+ Эти настройки по умолчанию обеспечивают надежную защиту. Но вы можете легко изменить их под ваши конкретные потребности.
+
+ Убрать
+
+
+ Нажмите здесь, чтобы удалить всё — историю, куки, всё — и начать заново на новой вкладке.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Закрыть
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Виджет поиска
+
+ !-- This is the title of promote search widget dialog. -->
+
+ История веб-сёрфинга удалена! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Начните сеанс приватного просмотра, и мы будем блокировать трекеры и другие плохие штуки.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Мы оставим вас в приватном просмотре, но в следующий раз можно войти в него быстрее с помощью виджета %1$s на Домашнем экране.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Добавить виджет на домашний экран
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Виджет добавлен на домашний экран
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-si/strings.xml b/mobile/android/focus-android/app/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000000..072628d497
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-si/strings.xml
@@ -0,0 +1,1078 @@
+
+
+
+
+
+
+
+
+ අවලංගු
+
+ හරි
+
+ සුරකින්න
+
+
+ සොයන්න හෝ ලිපිනය ලියන්න
+
+ ස්වයංක්රීය පෞද්. පිරික්සීම.\nපිරික්සන්න. මකන්න. නැවතත්.
+
+
+ ඔබගේ පිරික්සුම් ඉතිහාසය මකා දමා ඇත.
+ පිරික්සුම් ඉතිහාසය මැකිණි
+
+
+ පටිත්තෙහි පිරික්සුම් ඉතිහාසය මකා දමා ඇත.
+
+
+ %1$s සඳහා සොයන්න
+
+
+ බෙදාගන්න…
+
+
+ අඩවියේ දෝෂ වාර්තාව
+
+
+ %1$s හි අරින්න
+
+
+ මෙහි අරින්න…
+
+
+ මුල් තිරයට යොදන්න
+
+
+ කෙටිමං වෙත යොදන්න
+
+
+ කෙටිමං වෙතින් ඉවත් කරන්න
+
+
+ සැකසුම්
+ පිළිබඳව
+ උදව්
+ ඔබගේ අයිතීන්
+
+
+ ලුහුබැඳීම් අවහිරයි
+
+
+
+ මෙය අක්රිය කිරීමෙන් ඇතැම් අඩවිවල ගැටළු විසඳේ
+
+
+ අන්තර්ගත අවහිරය
+
+
+ ඇතැම් අඩවි නිරාකරණයට නවතන්න
+
+
+ %s මගින් බලගැන්වේ
+
+
+ හරහා බෙදාගන්න
+
+ පිරික්සුම් ඉතිහාසය මකන්නද?
+ ඔබගේ පිරික්සුම් ඉතිහාසය ආරක්ෂිතව මැකීමට මෙම දැනුම්දීම මත තට්ටු කරන්න හෝ හිස් කරන්න.
+
+
+ ඔබගේ පිරික්සුම් ඉතිහාසය ආරක්ෂිතව මැකීමට මෙම දැනුම්දීම මත තට්ටු කරන්න හෝ තල්ලු කරන්න.
+
+ පිරික්සුම් ඉතිහාසය මකන්න
+
+
+ අරින්න
+
+
+ මකා දමා අරින්න
+
+
+ මකන්න
+
+
+ පිරික්සුම් ඉතිහාසය මකන්න
+
+
+
+ මකා දමා අරින්න
+
+
+ මකා දමා %1$s අරින්න
+
+
+
+ ෆෝකස්හි සොයන්න
+
+ ක්ලාර්හි සොයන්න
+
+ ෆෝකස් බීටා හි සොයන්න
+
+ ෆෝකස් නයිට්ලි හි සොයන්න
+
+
+ %1$s ඔබව පාලනයේ තබයි.
+පෞද්. අතිරික්සුවක් ලෙස යොදාගන්න:
+
+ යෙදුමෙහි සොයන්න සහ පිරික්සන්න
+ ලුහුබැඳීම් අවහිර කරන්න (හෝ සැකසුම් තුළින් ඉඩ දෙන්න)
+ දත්තකඩ මෙන්ම සෙවුම් හා පිරික්සුම් ඉතිහාසය ඉවත් කිරීමට මකන්න
+
+
+මොසිල්ලා හරහා %1$s නිෂ්පාදනය කෙරේ. හිතකර, විවෘත අන්තර්ජාලයකට පිහිටාධාර සැපයීම අපගේ මෙහෙවරයි.
+තව දැනගන්න
]]>
+
+
+ පෞද්ගලිකත්වය හා ආරක්ෂාව
+
+
+ ලුහුබැඳීම්, දත්තකඩ, දත්ත තේරීම්
+
+
+ පෙරනිමි සකසන්න, ස්වයං පිරවීම
+
+
+
+
+ %1$s ගැන, උදව්
+
+
+ දියුණු කළ ලුහුබැඳීමේ රැකවරණය
+
+
+ වියමන අන්තර්ගතය
+
+
+ යෙදුම් මාරුව
+
+
+ සාමාන්ය
+
+
+ පෙරනිමි අතිරික්සුව, භාෂාව
+
+
+ දත්ත රැස් කිරීම හා භාවිතය
+
+ සොයන්න
+
+
+ සෙවුම් යෝජනා ගන්න
+
+
+ ඔබ ලිපින තීරුවේ ලියන දෑ ඔබගේ සෙවුම් යන්ත්රයට %1$s හරහා යවයි
+
+
+ පෙරනිමි
+
+
+ සෙවුම් යන්ත්රය
+
+
+ සක්රිය
+
+
+ අක්රිය
+
+
+ ඒ.ස.නි. ස්වයං පිරවීම
+
+
+ ප්රචලිත අඩවි සඳහා
+
+
+ ලිපින තීරුවේ %s ජනප්රිය ඒ.ස.නි. 450 කට වඩා ස්වයං පිරවීමට සබල කරන්න.
+
+
+ එක් කරන අඩවි සඳහා
+
+
+ %s ඔබගේ ප්රියතම ඒ.ස.නි. ස්වයං පිරවීම සබල කරන්න.
+
+
+ අඩවි කළමනාකරණය
+
+
+ අඩවි කළමනාකරණය
+
+
+ + අභිරුචි ඒ.ස.නි. යොදන්න
+
+
+ ඔබගේ ස්වයං පිරවුම් ලේඛනය:
+
+
+ ඒ.ස.නි. යොදන්න
+
+
+ අභිරුචි ඒ.ස.නි. යොදන්න
+
+
+ අභිරුචි ඒ.ස.නි. යොදන්න
+
+
+ ස්වයං පිරවීමට සබැඳිය යොදන්න
+
+
+ දත්තකඩ හා අඩවි දත්ත
+
+
+ දත්ත තේරීම්
+
+
+ අභිරුචි ඒ.ස.නි. ඉවත් කරන්න
+
+
+ තව දැනගන්න
+
+
+ අභිරුචි ස්වයං පිරවුම් ඒ.ස.නි. එක් කර කළමනාකරණය.
+
+
+ එක් කිරීමට ඒ.ස.නි.
+
+
+ අලවන්න හෝ ඒ.ස.නි. යොදන්න
+
+
+ නිදසුන: mozilla.org
+
+
+ නිදසුන: example.com
+
+
+ නව අභිරුචි ඒ.ස.නි. එක් කෙරිණි.
+
+
+ ඉවත් කරන්න
+
+
+ ඉවත් කරන්න
+
+
+ ඇතුල් කළ ඒ.ස.නි. දෙවරක් පරීක්ෂා කරන්න.
+
+ භාෂාව
+
+ පද්ධතියේ පෙරනිමි
+
+ පෞද්ගලිකත්වය
+
+ දැන්වීම් ලුහුබැඳීම් අවහිරය
+ ඔබ දැන්වීම් නොඑබුවත් ඇතැම් දැන්වීම් අඩවියට පැමිණීම් ලුහුබඳියි
+ විශ්ලේෂ ලුහුබැඳීම් අවහිරය
+ තට්ටු කිරීම හා අනුචලනය වැනි ක්රියාකාරකම් රැස් කිරීමට, විශ්ලේෂණයට සහ මැනීමට භාවිතා කරයි
+ සමාජ ලුහුබැඳීම් අවහිරය
+ ඔබගේ ගොඩවැදීම් ලුහුබැඳීමට හා බෙදාගැනීමේ බොත්තම් වැනි දෑ පෙන්වීමට අඩවි වෙත කාවද්දා තිබේ
+ අන් අන්තර්ගත ලුහුබැඳීම් අවහිරය
+ සබල කිරීමෙන් ඇතැම් පිටු අනපේක්ෂිත ලෙස හැසිරීමට හේතු වනු ඇත
+ දත්තකඩ අවහිරය
+
+
+ එපා, ස්තුතියි
+ තෙවන පාර්ශ්ව ලුහුබඳින දත්තකඩ අවහිරය
+ තෙවන පාර්ශ්ව දත්තකඩ අවහිරය
+ හරස් අඩවි දත්තකඩ අවහිරය
+
+ ඔව්, කරුණාකර
+
+
+ යෙදුම අගුළු ඇරීමට ඇඟිලි සටහන
+
+
+ ඔබ කෙටිමං එක් කර ඇත්නම් හෝ අඩවියක් දැනටමත් %s තුළ විවෘතව තිබෙන විට ඇඟිලි සටහන භාවිතයෙන් අගුළු හරින්න.
+
+
+ රහසිගත
+
+ යෙදුම් මාරු කිරීමේදී පිටු සඟවන්න සහ තිරසේයා ගැනීම අවහිර කරන්න.
+
+ ආරක්ෂාව
+
+ කාර්ය සාධනය
+ වියමන අකුරු අවහිරය
+
+
+ නිරූපක හෝ රූප මඟහැරීමට හේතු විය හැකිය
+
+ ජාවාස්ක්රිප්ට් අවහිරය
+
+
+ පිටු වේගයෙන් පූරණය වන නමුත් අනපේක්ෂිත ලෙස හැසිරිය හැකිය
+
+
+ %1$s පෙරනිමි අතිරික්සුව කරන්න
+
+ මොසිල්ලා
+ භාවිත දත්ත යවන්න
+
+
+ තව දැනගන්න
+
+
+ සැමට %1$s වැඩිදියුණු කර දීමට හා සැපයීමට අවශ්ය දෑ පමණක් රැස් කර ගැනීමට මොසිල්ලා උත්සාහ කරයි.
+
+
+ පෞද්ගලිකත්ව දැන්වීම
+
+
+ බලපත්රදායක තොරතුරු
+
+
+ අපි භාවිතා කරන එකතු
+
+
+ %s | OSS එකතු
+
+
+ %1$s ගැන
+
+
+ ස්ථාපිත සෙවුම් යන්ත්ර
+
+
+ සෙවුම් යන්ත්රය තෝරන්න
+
+
+ පෙරනිමි සෙවුම් එළවුම් ප්රත්යර්පණය
+
+
+ + අන් සෙවුම් යන්ත්රයක් එක් කරන්න
+ සෙවුම් යන්ත්ර ඉවත් කරන්න
+ ඉවත් කරන්න
+
+ අන් සෙවුම් යන්ත්රයක් එක් කරන්න
+
+
+ ප්රියතම යන්ත්රය තෝරන්න:
+
+
+ සෙවුම් යන්ත්රයක් යොදන්න
+
+ සෙවුම් යන්ත්රයේ නම
+ භාවිතයට සෙවුම් තන්තුව
+ සුරකින්න
+
+
+ උදාහරණය: නිදසුන.ලංකා/සොයන්න/?q=%s
+
+ නව සෙවුම් යන්ත්රයක් එක් විය.
+
+ සෙවුම් යන්ත්රයේ නම යොදන්න
+ ස්ථාපිත සෙවුම් යන්ත්රයක් දැනටමත් එම නම භාවිතා කරයි.
+
+ සෙවුම් පදය යොදන්න
+
+ සෙවුම් තන්තු උදාහරණ ආකෘතියට ගැළපෙන බව පරීක්ෂා කරන්න
+
+
+ ආදානය මකන්න
+
+
+ ඉවතලන්න
+
+
+ පිරික්සුම් ඉතිහාසය මකන්න
+
+
+ විවෘත පටිති: %1$s
+
+
+ ආරක්ෂිත සම්බන්ධතාවයකි
+
+
+ පූරණය වෙමින්
+
+
+ අඩවිය පූරණය විය
+
+
+ තවත් විකල්ප
+
+
+ තවත් විකල්ප බොත්තම
+
+
+ ඉදිරියට යාත්රණය
+
+
+ අඩවිය යළි පූරණය
+
+
+ ආපසු යාත්රණය
+
+
+ අඩවිය පූරණය වීම නවතන්න
+
+
+ කලින් යෙදුමට ආපසු
+
+
+ අවහිර කළ ලුහුබැඳීම් ගණන
+
+
+ ලුහුබැඳීම් අවහිරය
+
+ ඔබගේ අයිතීන්
+
+ අන් යෙදුමක සබැඳිය අරින්න
+
+ මෙම සබැඳිය %2$s හි විවෘත කිරීමට %1$s හැර යාමට හැකිය.
+
+ සබැඳිය විවෘත කළ හැකි යෙදුමක් සොයන්න
+
+ ඔබගේ උපාංගයේ තිබෙන යෙදුම් කිසිවකට මෙම සබැඳිය විවෘත කළ නොහැකිය. %2$s හි යෙදුමක් සොයා ගැනීම සඳහා %1$s හැර යාමට හැකිය.
+
+ පෞද්. පිරික්සුමෙන් පිටවන්නද?
+
+
+ %1$s අවසන්
+
+
+ අරින්න
+
+
+ කෙටිමං වෙත එක් කෙරිණි!
+
+ සේවාදායකය හමු නොවුණි
+
+
+ වසන්න
+
+
+
+ %1$s වෙත පිළිගනිමු
+
+
+ වේගවත්. පෞද්ගලික. බාධා රහිත.
+
+
+ පටන් ගන්න
+
+
+
+ %1$s වෙනත් අතිරික්සු මෙන් නොවේ
+
+
+ අතිරේක පෞද්ගලිකත්වයක් උදෙසා යෙදුම වසා දැමූ විට ඔබගේ ඉතිහාසය හිස් කෙරේ.
+
+
+
+ විවෘත වන සෑම සබැඳියක් සමඟ ඔබගේ දත්ත රැකවරණයට %1$s පෙරනිමි කරන්න.
+
+
+ පෙරනිමි අතිරික්සුව කරන්න
+
+
+ මඟහරින්න
+
+
+
+ ඔබගේ පෞද්ගලිකත්වය බලගන්වන්න
+
+ පෞද්ගලික පිරික්සීම ඊළඟ මට්ටමට ගෙන යන්න. අඩවි හරහා ඔබව ලුහුබැඳීමට හැකි දැන්වීම් හා වෙනත් අන්තර්ගත අවහිර කර පිටු පූරණ කාලය අවම කර ගන්න.
+
+
+ ඔබගේ සෙවීම, ඔබගේ මාවත
+
+ වෙනස් දෙයක් සොයනවාද? සැකසුම් තුළ වෙනත් පෙරනිමි සෙවුම් යන්ත්රයක් තෝරන්න.
+
+
+ ඔබගේ මුල් තිරයට කෙටිමං එක් කරන්න
+
+
+ ඉක්මනින් %1$s තුළ ඔබගේ ප්රියතම අඩවි වෙත ආපසු යන්න. %1$s වට්ටෝරුවෙන් \"මුල් තිරයට යොදන්න\" තෝරන්න.
+
+
+ පෞද්ගලිකත්වය පුරුද්දක් කරගන්න
+
+ ඔබගේ පෙරනිමි අතිරික්සුව ලෙස %1$s සකසා ඔබ වෙනත් යෙදුම්වලින් වියමන පිටු විවෘත කරන විට පෞද්ගලික පිරික්සුමෙහි ප්රතිලාභ අත්විඳින්න.
+
+ හරි, තේරුණා!
+ මඟහරින්න
+ ඊළඟ
+
+
+ -
+
+
+ එකතු
+
+ ඔව්
+
+
+ අවලංගු
+
+
+ නැහැ
+
+
+ දියුණු කළ ලුහුබැඳීමේ ආරක්ෂාව රහිතව කෙටිමඟ විවෘත වනු ඇත
+
+
+ පෞද්. පිරික්සුම් වාරය
+
+
+ ඔබගේ %1$s වාරය තට්ටු කිරීමකින් මකා දැමීමට දැනුම්දීම් ඔබට ඉඩ සලසයි. යෙදුම විවෘත කිරීමට හෝ ඔබගේ අතිරික්සුවෙහි ධාවනය වන දෑ බැලීමට අවශ්ය නොවේ.
+
+
+ පිරික්සුම් ඉතිහාසය මකන්න
+
+
+ ෆයර්ෆොක්ස් බාගන්න
+
+
+
+
+
+ මොසිල්ලා පොදු බලපත්රය සහ අනෙකුත් විවෘත මූලාශ්ර බලපත්රවල නියම යටතේ ඔබට %1$s ලබා ගැනීමට හැකිය.]]>
+
+
+ මෙතැන තිබේ.]]>
+
+
+ බලපත්ර යටතේ තිබේ.]]>
+
+
+ ජීඑන්යූ සාමාන්ය පොදු බලපත්රය අනු. 3 යටතේ ස්වාධීන ව්යාපෘතියකි, සහ මෙහි තිබේ.]]>
+
+
+ පරිශීලක නාමය
+ මුරපදය
+ මකන්න
+
+
+
+ ආරක්ෂිත සම්බන්ධතාවයකි
+ ආනාරක්ෂිත සම්බන්ධතාවයකි
+
+ සත්යාපනය: %1$s
+
+
+ අඩවියේ ආරක්ෂාව
+ ඒ.ස.නි. දැනටමත් පවතී
+
+
+ පිටුවේ සොයන්න
+
+
+ පිටුවේ සොයන්න
+
+
+ %1$d/%2$d
+
+
+ %1$d / %2$d
+
+
+ ඊළඟ ප්රතිඵලය සොයන්න
+
+ කලින් ප්රතිඵලය සොයන්න
+
+
+ පිටුවේ සෙවීම ඉවතලන්න
+
+
+ වැඩතල අඩවිය ඉල්ලන්න
+
+
+ වැඩතල අඩවිය
+
+
+ ඒ.ස.නි. පිටපත් විය
+
+
+ සංවර්ධක මෙවලම්
+
+
+ යෙදුම්වල සබැඳි අරින්න
+
+
+ වැඩිදුර
+
+
+ අඩවි අවසර
+
+
+ දත්තකඩ පතාක අවකරණය
+
+
+ සක්රියයි
+
+
+ අක්රියයි
+
+
+ දත්තකඩ පතාක අවකරණය
+
+
+ පිළිවන් විට දත්තකඩ ඉල්ලීම් ස්වයංක්රීයව ඉවතලීමෙන් පතාක අඩුවෙන් පෙන්වයි.
+
+ -->
+ දත්තකඩ පතාක අවකරණය
+
+
+ මෙම අඩවියට සක්රියයි
+
+
+ අඩවියට සහාය නොදක්වයි
+
+
+ මෙම අඩවියට අක්රියයි
+
+
+ දත්තකඩ පතාක අවකරණය
+
+
+ මෙම අඩවියට අක්රියයි
+
+
+ මෙම අඩවියට සක්රියයි
+
+
+ %1$s සඳහා දත්තකඩ පතාක අවකරණය සක්රිය කරන්නද?
+
+
+ %1$s සඳහා දත්තකඩ පතාක අවකරණය අක්රිය කරන්නද?
+
+
+ %1$s මෙම අඩවියේ දත්තකඩ ඉවත් කර පිටුව නැවුම් කරයි. සියළුම දත්තකඩ මැකීමෙන් බඩු කරත්ත හිස් වීමට හෝ ඔබව නික්මවීමට ඉඩ ඇත.
+
+
+ %1$s මඟින් දත්තකඩ ඉල්ලීම් ස්වයංක්රීයව ඉවතලීමට හැකිය.
+
+
+ මෙම අඩවිය දැනට දත්තකඩ පතාක අවකරණයට සහාය නොදක්වයි. මෙම වියමන අඩවිය සමාලෝචනය කර අනාගතයේදී සහාය දක්වන ලෙස අපගේ කණ්ඩායමට දැන්වීමට කැමතිද?
+
+
+ අවලංගු
+
+
+ සහාය ඉල්ලන්න
+
+
+ අඩවිය සඳහා සහාය ඉල්ලීම යොමු කෙරිණි.
+
+
+ අඩවිය සඳහා සහාය ඉල්ලීම යොමු කෙරිණි.
+
+
+
+ කරදරකාරී දත්තකඩ පතාක ඉවතලීම සඳහා දත්තකඩ ඉල්ලීම් ප්රතික්ෂේප කිරීමට %1$s උත්සාහ කරයි.\n\n%2$s තුළ දත්තකඩ පතාක අභිප්රේත කළමනාකරණය කරන්න.
+
+
+ සැකසුම්
+
+
+ ස්වයං වාදනය
+
+
+ එයට ඉඩ දීමට:
+
+
+ 1. ඇන්ඩ්රොයිඩ් සැකසුම් වෙත යන්න
+
+
+ අවසර තට්ටු කරන්න]]>
+
+
+ සැකසුම් වෙත යන්න
+
+
+ %1$s සබල කරන්න]]>
+
+
+ රූගතය
+
+
+ ශබ්දවාහිනිය
+
+
+ ස්ථානය
+
+
+ දැනුම්දීම්
+
+
+ DRM-පාලිත අන්තර්ගතය
+
+
+ ඉඩදීමට අසන්න
+
+
+ අවහිරයි
+
+
+ ඉඩ දී ඇත
+
+
+ ඇන්ඩ්රොයිඩ් මගින් අවහිරයි
+
+
+ ශ්රව්ය හා දෘශ්ය සඳහා ඉඩදෙන්න
+
+
+ හඬ පමණක් අවහිරය
+
+
+ නිර්දේශිත
+
+
+ ශ්රව්ය සහ දෘශ්ය අවහිරය
+
+
+ අධ්යන
+
+
+ ෆයර්ෆොක්ස් මගින් කලින් කලට අධ්යන ස්ථාපනය කර ධාවනය කරයි.
+
+
+ තව දැනගන්න
+
+
+ වෙනස්කම් යෙදීම සඳහා යෙදුම ඉවත් වනු ඇත
+
+
+ ඉවත් කරන්න
+
+
+ සක්රිය
+
+
+ සම්පූර්ණයි
+
+
+ USB/වයි-ෆයි හරහා දුරස්ථ නිදොස්කරණය
+
+
+ අගුළු හරින්න
+
+
+ ඇඟිලි සටහන භාවිතයෙන් තහවුරු කරන්න
+
+
+ වත්මන් වාරය සමඟ ඉදිරියට යාමට ඔබගේ ඇඟිලි සටහන භාවිතා කිරීමට හැකිය.
+
+
+ නව පටිත්තකින් වාරය අරින්න
+
+
+ ඇඟිලි සටහන් නිරූපකය
+
+
+ ඇඟිලි සටහන හඳුනාගෙන නැත. යළි උත්සාහ කරන්න.
+
+
+ ඇඟිල්ල වේගයෙන් චලනය විය. යළි උත්සාහ කරන්න.
+
+
+ සෙවුම් යෝජනා පෙන්වන්නද?
+
+
+ යෝජනා ලබා ගැනීමට, ඔබ ලිපින තීරුවේ ලියන දෑ සෙවුම් යන්ත්රය වෙත යැවීම %1$s මගින් සිදු කළ යුතුය.
+
+
+ නැහැ
+
+
+ ඔව්
+
+
+ ඇතැම් සෙවුම් යන්ත්රවලට යෝජනා පෙන්වීමට නොහැකිය.
+
+
+ ඉවතලන්න
+
+
+
+
+ අඩවිය අනපේක්ෂිත ලෙස හැසිරෙන්නේද?\n
+ ලුහුබැඳීමේ රැකවරණය අක්රිය කර බලන්න
+
+
+ මුල් තිරයට යොදන්න]]>
+
+
+ සෑම සබැඳියක්ම %1$s\n හි විවෘත කරන්න
+ %1$s පෙරනිමි අතිරික්සුව ලෙස සකසන්න
+
+
+
+ නිතර භාවිතා කරන අඩවි සඳහා ඒ.ස.නි. ස්වයං පිරවීම\n
+ ලිපින තීරුවේ ඕනෑම ඒ.ස.නි. මත මද වේලාවක් ඔබන්න
+
+
+
+ සබැඳියක් නව පටිත්තක විවෘත කරන්න\n
+ පිටුවක ඇති ඕනෑම සබැඳියක් මද වේලාවක් ඔබන්න
+
+
+
+ ආරම්භක තිරය මත ඉඟි අක්රිය කරන්න
+
+
+ නව පටිත්තක් විවෘතයි
+
+
+ මාරු වන්න
+
+
+ පූර්ණ තිර ප්රකාරයට ඇතුල් වෙමින්
+
+
+ වහාම නව පටිත්තක සබැඳියට මාරු වන්න
+
+
+ භයානක හා කූට අන්තර්ගත අවහිර කරන්න
+
+ වාර්තා වූ කූට හා ප්රහාරක අඩවි, අනිෂ්ට අඩවි, සහ අනවශ්ය මෘදුකාංග අඩවි අවහිර කරන්න.
+
+ HTTPS-පමණි ප්රකාරය
+
+
+ ඉහළ ආරක්ෂාවක් සඳහා HTTPS සංකේතන කෙටුම්පත භාවිතයෙන් අඩවි වෙත ස්වයංක්රීයව සම්බන්ධ වීමට තැත් කරයි.
+
+
+ හැරදැමීම්
+
+ ඔබ මෙම අඩවි සඳහා අන්තර්ගත අවහිර කිරීම අබල කර ඇත.
+
+ ඉවත් කරන්න
+
+ සියළු අඩවි ඉවත් කරන්න
+
+
+ දත්තකඩ අවහිරය
+
+
+ ඔබ දත්තකඩ අවහිර කිරීමට කැමතිද?
+
+
+ පටිත්ත බිඳ වැටුණි
+
+ කණගාටුයි. මෙම පටිත්ත සමඟ ගැටලුවක් තිබේ.
+
+ පෞද්. අතිරික්සුවක් බැවින් සුරැකීමට හෝ මෙම පටිත්ත ප්රත්යර්පණය කළ නොහැකිය.
+
+ පටිත්ත වසන්න
+
+
+ මොසිල්ලා වෙත බිඳ වැටීම් වාර්තාව යවන්න
+
+
+
+
+ %s සිට අවහිර කළ ලුහුබැඳීම්
+
+ අන්තර්ගතය
+
+ දැන්වීම්
+
+ සමාජ
+
+ විශ්ලේෂ
+
+ දියුණු කළ ලුහුබැඳීමේ රැකවරණය
+
+ අඩවියට දියුණු කළ ලුහුබැඳීමේ රැකවරණය අක්රියයි
+
+ අඩවියට දියුණු කළ ලුහුබැඳීමේ රැකවරණය සක්රියයි
+
+ සම්බන්ධතාවය ආරක්ෂිතයි
+
+ සම්බන්ධතාවය අනාරක්ෂිතයි
+
+ අවහිර කිරීමට ලුහුබැඳීම් හා අත්පත්
+
+
+ ආපසු යන්න
+
+
+
+ ඉවත් කරන්න
+
+ නම් කරන්න
+
+ නම් කරන්න
+
+ කෙටිමඟ නාමය
+
+
+ ඔබ %1$s ඉතිහාසය මකන විට සුරකින ලද සහ බෙදා ගත් රූප <b>මකනු නොලැබේ</b>
+
+
+
+ තේමාව
+
+ දීප්ත
+
+ අඳුරු
+
+ විදුලිකෝෂ සුරැකුමෙන් සැකසූ
+
+ උපාංගයේ තේමාව
+
+
+ අඩවිය HTTPS සඳහා සහය නොදක්වයි
+
+
+ තව දැනගන්න
+ සැකසුම් > පෞද්ගලිකත්වය හා ආරක්ෂාව > ආරක්ෂාව හරහා සැකසුම වෙනස් කරන්න.]]>
+
+
+ සම්බන්ධතාවය අනාරක්ෂිතයි
+
+
+
+ ඔබ කලින් මෙම සේවාදායකයට සාර්ථකව සම්බන්ධ වී ඇත්නම්, මෙය තාවකාලික දෝෂයක් විය හැකිය.
+ ]]>
+
+
+ යමෙක් වංචනිකව මෙම අඩවිය හසුරුවීමට තැත් කරන හෙයින් ඉදිරියට යාම අවදානම් සහගතය.
+
+ %2$s හි සහතිකයේ නිකුත්කරු නොදන්නා නිසා %1$s එය විශ්වාස නොකරයි, සහතිකය ස්වයං-අත්සන් කර හෝ සේවාදායකය නිවැරදි අතරමැදි සහතිකය එවන්නේ නැත.
+ ]]>
+
+
+
+ පටිත්ත වසන්න
+
+
+
+ ඒවා ලැබුණා! අපි මෙම අඩවිය ඔබ ගැන ඔත්තු බැලීම නැවැත්තුවෙමු. අපි අවහිර කරන දෑ බැලීමට පලිහ මත තට්ටු කරන්න.
+
+
+ උත්පතනය වසන්න
+
+
+
+ ඔබ සුරක්ෂිතයි!
+
+ මෙම පෙරනිමි සැකසුම් ඉහළ ආරක්ෂාවක් සපයයි. නමුත් ඔබගේ අවශ්යතා වලට යෝග්ය ලෙස සැකසුම් හැඩගැසීමට පහසුය.
+
+ ඉවතලන්න
+
+
+ ඉතිහාසය, දත්තකඩ, හා අනෙකුත් දෑ — සියල්ල කුණු කූඩයට දැමීමට මෙහි තට්ටු කරන්න — සහ නව පටිත්තකින් නැවුම්ව අරඹන්න.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ වසන්න
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ පිරික්සුම් ඉතිහාසය මැකිණි! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ ඔබගේ පෞද්ගලික පිරික්සුම් වාරය අරඹන්න, ඔබ යන හැටියට ලුහුබැඳීම් සහ වෙනත් නරක දෑ අවහිර කෙරේ.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sk/strings.xml b/mobile/android/focus-android/app/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000000..886c54f665
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sk/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ Zrušiť
+
+ OK
+
+ Uložiť
+
+
+ Zadajte hľadanie alebo adresu
+
+ Automatické súkromné prehliadanie.\nPrehliadajte. Vymažte históriu. Opakujte.
+
+
+ Vaša história prehliadania bola vymazaná.
+
+ História prehliadania bola vymazaná
+
+
+ História bola vymazaná.
+
+
+ Vyhľadať %1$s
+
+
+ Zdieľať…
+
+
+ Nahlásiť problém so stránkou
+
+
+ Otvoriť v aplikácii %1$s
+
+
+ Otvoriť pomocou…
+
+
+ Pridať na úvodnú obrazovku
+
+
+ Pridať medzi Skratky
+
+ Odstrániť zo Skratiek
+
+
+ Nastavenia
+ O aplikácii
+ Pomocník
+ Vaše práva
+
+
+ Počet zablokovaných sledovacích prvkov
+
+
+ Vypnutie tejto funkcie môže odstrániť niektoré problémy
+
+
+ Blokovanie obsahu
+
+ Vypnutie tejto funkcie odstráni niektoré problémy so stránkami
+
+
+ Powered by %1$s
+
+
+ Zdieľať cez
+
+ Vymazať históriu prehliadania?
+ Ťuknutím alebo vymazaním tohto upozornenia bezpečne vymažete svoju históriu prehliadania.
+
+
+ Ťuknutím alebo potiahnutím tohto upozornenia bezpečne vymažete históriu prehliadania.
+
+ Vymazať históriu prehliadania
+
+
+ Otvoriť
+
+
+ Vymazať a otvoriť
+
+
+ Vymazať
+
+
+ Vymazať históriu
+
+
+
+ Vymazať a otvoriť
+
+
+ Vymazať a otvoriť %1$s
+
+
+
+ Hľadať vo Focuse
+
+ Hľadať v Klare
+
+ Hľadať vo Focuse Beta
+
+ Hľadať vo Focuse Nightly
+
+
+ %1$s vám dáva kontrolu.
+Používajte ho ako súkromný prehliadač:
+
+ Hľadajte a prehliadajte priamo v aplikácii
+ Blokujte sledovacie prvky (alebo aktualizujte nastavenia a povoľte niektoré sledovacie prvky)
+ Jednoducho odstraňujte súbory cookie, ako aj históriu vyhľadávania a prehliadania
+
+
+%1$s je vytváraný spoločnosťou Mozilla. Našim poslaním je podporovať zdravý a otvorený internet.
+Ďalšie informácie
]]>
+
+
+ Súkromie a bezpečnosť
+
+
+ Sledovanie, súbory cookie, údaje o používaní
+
+
+ Predvolené nastavenia, automatické dopĺňanie
+
+
+
+
+ O aplikácii %1$s, pomocník
+
+
+ Rozšírená ochrana pred sledovaním
+
+
+ Webový obsah
+
+
+ Prepínanie medzi aplikáciami
+
+
+ Všeobecné
+
+
+ Predvolený prehliadač, jazyk
+
+
+ Zbieranie údajov a ich použitie
+
+ Vyhľadávanie
+
+
+ Zobrazovať návrhy vyhľadávania
+
+ %1$s bude odosielať vami napísaný text v paneli s adresou vyhľadávaciemu modulu
+
+
+ Predvolený
+
+
+ Vyhľadávací modul
+
+
+ Zapnuté
+
+
+ Vypnuté
+
+
+ Automatické dokončovanie URL adries
+
+
+ Pre top stránky
+
+
+ Povolením tejto možnosti umožníte aplikácii %s automaticky dokončovať viac než 450 populárnych adries URL.
+
+
+ Pre vami pridané stránky
+
+
+ Ak chcete, aby %s automaticky dopĺňal vaše obľúbené adresy, aktivujte túto funkciu.
+
+
+ Spravovať stránky
+
+
+ Spravovať stránky
+
+
+ + Pridať vlastnú URL
+
+
+ Váš zoznam automatického dopĺňania:
+
+
+ Pridať adresu URL
+
+
+ Pridať vlastnú URL
+
+
+ Pridať vlastnú URL
+
+
+ Pridať odkaz na automatické dokončovanie
+
+
+ Cookies a údaje stránok
+
+
+ Odosielanie údajov
+
+
+ Odstrániť vlastné URL
+
+
+ Ďalšie informácie
+
+
+ Pridanie a správa vlastných URL adries na dokončovanie.
+
+
+ Adresa na pridanie
+
+
+ Prilepte alebo zadajte URL adresu
+
+
+ Príklad: mozilla.org
+
+
+ Príklad: example.com
+
+
+ Bola pridaná nová vlastná URL adresa.
+
+
+ Odstrániť
+
+
+ Odstrániť
+
+
+ Skontrolujte zadanú URL adresu.
+
+ Jazyk
+
+ Jazyk zariadenia
+
+ Súkromie
+ Blokovať reklamné sledovacie prvky
+ Niektoré reklamy vás sledujú aj keď na ne nekliknete
+ Blokovať analytické sledovacie prvky
+ Tieto sa používajú na zbieranie a analýzu údajov o aktivitách, napríklad o posúvaní sa na stránke
+ Blokovať sledovacie prvky sociálnych sietí
+ Tieto sa používajú na sledovanie vašich návštev a na zobrazovanie špecifických funkcií, napríklad tlačidla na zdieľanie
+ Blokovať ostatné sledovacie prvky
+ Niektoré stránky sa môžu chovať neočakávane
+ Blokovať cookies
+
+
+ Nie, ďakujem
+ Len sledovacie cookies tretích strán
+ Len cookies tretích strán
+
+ Blokovať súbory cookie tretích strán
+ Áno, prosím
+
+
+ Na odomknutie aplikácie používať odtlačok prsta
+
+
+ Odomknite pomocou odtlačku prsta, ak ste pridali Skratky alebo keď je už webová lokalita otvorená v %s.
+
+
+ Nenápadnosť
+
+ Skryje náhľad webových stránok pri zobrazení nedávnych aplikácii a zablokuje ukladanie snímok obrazovky.
+
+ Bezpečnosť
+
+ Výkon
+ Blokovať webové písma
+
+ Niektoré ikony alebo obrázky sa nemusia zobrazovať
+
+ Blokovať JavaScript
+
+ Stránky sa môžu načítavať rýchlejšie, no môžu sa správať nepredvídateľne
+
+
+ Nastaviť %1$s ako predvolený prehliadač
+
+ Mozilla
+ Odosielať údaje o používaní
+
+
+ Ďalšie informácie
+
+
+ Mozilla sa usiluje zbierať len údaje, ktoré potrebujeme na vylepšenie aplikácie %1$s pre všetkých.
+
+
+ Vyhlásenie o ochrane osobných údajov
+
+
+ Informácie o licenciách
+
+
+ Knižnice, ktoré používame
+
+
+ %s | Knižnice OSS
+
+
+ O aplikácii %1$s
+
+
+ Nainštalované vyhľadávacie moduly
+
+
+ Zvoľte vyhľadávací modul
+
+
+ Obnoviť predvolené vyhľadávacie moduly
+
+
+ + Pridať ďalší vyhľadávací modul
+ Odstrániť vyhľadávacie moduly
+ Odstrániť
+
+
+ Pridať ďalší vyhľadávací modul
+
+ Vyberte si vami požadovaný modul:
+
+
+ Pridať vyhľadávací modul
+
+ Názov vyhľadávacieho modulu
+ Výraz vyhľadávania
+ Uložiť
+
+
+ Príklad: example.com/search/?q=%s
+
+ Bol pridaný nový vyhľadávací modul.
+
+ Zadajte názov vyhľadávacieho modulu
+ Tento názov už používa nainštalovaný vyhľadávací modul.
+
+ Zadajte hľadaný výraz
+
+ Skontrolujte, či sa hľadaný výraz formátovo zhoduje s príkladom
+
+
+ Vymazať pole
+
+
+ Zavrieť
+
+
+ Vymazať históriu prehliadania
+
+
+ Otvorené karty: %1$s
+
+
+ Zabezpečené pripojenie
+
+
+ Načítava sa
+
+
+ Stránka bola načítaná
+
+
+ Ďalšie možnosti
+
+
+ Tlačidlo ďalších možností
+
+
+ Prejsť dopredu
+
+
+ Obnoviť stránku
+
+
+ Prejsť dozadu
+
+
+ Zastaviť načítavanie webovej stránky
+
+
+ Návrat do predchádzajúcej aplikácie
+
+
+ Počet zablokovaných sledovacích prvkov
+
+
+ Blokovať sledovacie prvky
+
+ Vaše práva
+
+ Otvoriť odkaz v inej aplikácii
+
+ Môžete opustiť aplikáciu %1$s a otvoriť tento odkaz v aplikácii %2$s.
+
+ Nájsť aplikáciu, ktorá môže otvoriť tento odkaz
+
+ Žiadna aplikácia na vašom zariadení nie je schopná otvoriť tento odkaz. Môžete opustiť aplikáciu %1$s a nájsť vhodnú aplikáciu v %2$s.
+
+ Ukončiť režim Súkromné prehliadanie?
+
+
+ Hotovo pre %1$s
+
+
+ Otvoriť
+
+
+
+
+
+
+
+
+
+
+ Pridané medzi skratky!
+
+ Server sa nenašiel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zavrieť
+
+
+
+ Víta vás %1$s
+
+
+ Rýchly. Zameraný na súkromie. Žiadne rozptyľovanie.
+
+
+ Začíname
+
+
+
+ %1$s nie je ako iné prehliadače
+
+
+ Po zatvorení aplikácie vymažeme vašu históriu, aby ste získali väčšie súkromie.
+
+
+
+ Nastavte si %1$s ako predvolený, aby ste ochránili svoje údaje pri každom otvorení odkazu.
+
+
+ Nastaviť ako predvolený prehliadač
+
+
+ Preskočiť
+
+
+
+ Posuňte svoje súkromie na vyššiu úroveň
+
+ Súkromné prehliadanie na úplne inej úrovni. Blokujte reklamy a ostatný obsah, ktorý vás môže sledovať naprieč stránkami.
+
+
+ Prehliadanie podľa vás
+
+ Hľadáte niečo iné? Vyberte si iný predvolený vyhľadávací modul v nastaveniach.
+
+
+ Pridajte si na svoju úvodnú obrazovku skratky
+
+ S aplikáciou %1$s sa môžete vrátiť ku svojim obľúbeným stránkam prakticky okamžite. Stačí, ak v ponuke aplikácie %1$s vyberiete možnosť „Pridať na úvodnú obrazovku“.
+
+
+ Zvyknite si na súkromie
+
+ Nastavte si %1$s ako svoj predvolený prehliadač a získajte výhody súkromného prehliadania vždy, keď otvoríte webové stránky z iných aplikácií.
+
+ Ok, rozumiem!
+ Preskočiť
+ Ďalej
+
+
+ -
+
+
+ Pridať
+
+
+ ÁNO
+
+
+ Zrušiť
+
+
+ NIE
+
+
+ Skratka sa otvorí s vypnutou ochranou pred sledovaním
+
+
+ Súkromné prehliadanie
+
+
+ Upozornenia aplikácie %1$s vám umožnia vymazať históriu jedným ťuknutím. Nemusíte ani otvárať aplikáciu.
+
+
+ Vymazať históriu prehliadania
+
+
+ Stiahnite si Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License a ďalších licencií pre open source.]]>
+
+
+ na týchto stránkach.]]>
+
+
+ licenciami.]]>
+
+
+ GNU General Public License v3, ktoré sú dostupné na týchto stránkach .]]>
+
+
+ Používateľské meno
+ Heslo
+ Vymazať
+
+
+
+ Zabezpečené pripojenie
+ Nezabezpečené pripojenie
+
+ Overil ju %1$s
+
+
+ Zabezpečenie stránky
+ URL adresa už existuje
+
+
+ Hľadať na stránke
+
+
+ Hľadať na stránke
+
+
+ %1$d/%2$d
+
+ %1$d z %2$d
+
+
+ Nájsť ďalší výsledok
+
+ Nájsť predchádzajúci výsledok
+
+ Zavrieť hľadanie na stránke
+
+
+
+
+ Verzia pre počítače
+
+
+ Verzia pre počítače
+
+
+ Adresa bola skopírovaná
+
+
+ Vývojárske nástroje
+
+
+ Otvárať odkazy v aplikáciách
+
+
+ Rozšírené
+
+
+ Oprávnenia stránok
+
+
+ Zníženie počtu bannerov k súborom cookie
+
+
+ Zapnuté
+
+
+ Vypnuté
+
+
+ Zníženie počtu bannerov k súborom cookie
+
+
+ Zobrazuje menej bannerov automatickým odmietnutím žiadostí o použitie súborov cookie (kde je to možné).
+
+ -->
+ Zníženie počtu bannerov k súborom cookie
+
+
+ ZAPNUTÉ pre túto stránku
+
+
+ Stránka momentálne nie je podporovaná
+
+
+ VYPNUTÉ pre túto stránku
+
+
+ Zníženie počtu bannerov k súborom cookie
+
+
+ VYPNUTÉ pre túto stránku
+
+
+ ZAPNUTÉ pre túto stránku
+
+
+ Zapnúť znižovanie počtu bannerov k súborom cookie pre %1$s?
+
+
+ Vypnúť znižovanie počtu bannerov k súborom cookie pre %1$s?
+
+
+ %1$s vymaže súbory cookie tohto webu a obnoví stránku. Vymazanie všetkých súborov cookie vás môže odhlásiť zo stránky alebo vyprázdniť nákupné košíky.
+
+
+ %1$s sa môže pokúsiť automaticky odmietnuť žiadosti o súbory cookie.
+
+
+ Na tejto stránke momentálne nie je podporované znižovanie počtu súborov cookie. Chceli by ste požiadať náš tím o kontrolu tejto webovej stránky a pridanie podpory v budúcnosti?
+
+
+ Zrušiť
+
+
+ Vyžiadať podporu
+
+
+ Žiadosť o podporu stránky bola odoslaná.
+
+
+ Žiadosť o podporu stránky bola odoslaná.
+
+
+
+ %1$s sa pokúša odmietnuť žiadosti o súbory cookie, aby zrušil nepríjemné bannery k súborom cookie.\n\nPredvoľby správania bannerov k súborom cookie môžete spravovať v %2$s.
+
+
+ nastaveniach
+
+
+ Automatické prehrávanie
+
+
+ Postup pre povolenie:
+
+
+ 1. Prejdite do nastavení Androidu
+
+
+ Povolenia]]>
+
+
+ Prejsť do nastavení
+
+
+ %1$s do polohy ZAPNUTÉ]]>
+
+
+ Kamera
+
+
+ Mikrofón
+
+
+ Poloha
+
+
+ Upozornenia
+
+
+ Obsah chránený pomocou DRM
+
+
+ Vždy sa opýtať
+
+
+ Blokované
+
+
+ Povolené
+
+
+ Blokované systémom Android
+
+
+ Povoliť zvuk a video
+
+
+ Blokovať len zvuk
+
+
+ Odporúčané
+
+
+ Blokovať zvuk a video
+
+
+ Štúdie
+
+
+ Firefox môže občas inštalovať a spúšťať štúdie.
+
+
+ Ďalšie informácie
+
+
+ Aplikácia sa ukončí, aby sa mohli prejaviť zmeny
+
+
+ Odstrániť
+
+
+ Aktívne
+
+
+ Ukončené
+
+
+ Vzdialené ladenie cez USB/Wi-Fi
+
+
+ Odomknúť
+
+
+ Potvrďte pomocou odtlačku prsta
+
+
+ Na pokračovanie aktuálnej relácie aplikácie môžete použiť odtlačok prsta.
+
+
+ Otvoriť odkaz v novej relácii
+
+
+ Ikona odtlačku prsta
+
+
+ Odtlačok prsta nebol rozpoznaný. Skúste to znova.
+
+
+ Pohli ste prstom príliš rýchlo. Skúste to znova.
+
+
+ Zobrazovať návrhy vyhľadávania?
+
+
+ Ak chcete získavať návrhy vyhľadávania, %1$s bude odosielať vami písaný text vyhľadávaciemu modulu.
+
+
+ Nie
+
+
+ Áno
+
+
+ Niektoré vyhľadávacie moduly nedokážu zobrazovať návrhy vyhľadávania.
+
+
+ Zavrieť
+
+
+
+
+ Správa sa stránka neočakávane?\n Skúste vypnúť Ochranu pred sledovaním
+
+
+ Pridať na úvodnú obrazovku]]>
+
+
+ Otvárajte každý odkaz v aplikácii %1$s\n Nastavte si %1$s ako predvolený prehliadač
+
+
+ Automatické dopĺňanie URL adries je tu\n Podržte prst na akejkoľvek adrese v paneli s adresou
+
+
+ Otvorte odkaz na novej karte\n Podržte prst na akomkoľvek odkaze na stránke
+
+
+ Vypnúť tipy na úvodnej obrazovke aplikácie
+
+
+ Bola otvorená nová karta
+
+
+ Prepnúť
+
+
+ Bol spustený režim celej obrazovky
+
+
+ Prepnúť na odkaz na novej karte okamžite
+
+
+ Blokovať potenciálne nebezpečné a podvodné stránky
+
+ Blokovať nahlásené podvodné a útočné stránky, stránky s malvérom a nevyžiadaným softvérom.
+
+
+ Režim "Len HTTPS"
+
+
+ Automaticky sa pokúša pripojiť k stránkam pomocou šifrovacieho protokolu HTTPS na zvýšenie bezpečnosti.
+
+
+ Výnimky
+
+ Na týchto stránkach ste vypli blokovanie obsahu.
+
+ Odstrániť
+
+ Odstrániť všetky stránky
+
+
+ Blokovanie cookies
+
+
+ Chcete zablokovať súbory cookie?
+
+
+ Karta zlyhala
+
+ Ospravedlňujeme sa, no vyskytol sa problém s touto kartou.
+
+ Keďže sme súkromný prehliadač, nikdy neukladáme karty a nemôžeme ich tým pádom ani obnoviť.
+
+ Zavrieť kartu
+
+
+
+
+
+ Odoslať správu o zlyhaní Mozille
+
+
+
+
+ Sledovacie prvky zablokované od %s
+
+ Obsah
+
+ Reklamy
+
+ Sociálne siete
+
+ Analytika
+
+ Rozšírená ochrana pred sledovaním
+
+ Ochrana je na tejto stránke vypnutá
+
+ Ochrana je na tejto stránke zapnutá
+
+ Pripojenie je zabezpečené
+
+ Pripojenie nie je zabezpečené
+
+ Sledovacie prvky a skripty na zablokovanie
+
+
+ Prejsť naspäť
+
+
+
+ Odstrániť
+
+
+ Premenovať
+
+ Premenovanie
+
+
+ Názov skratky
+
+
+ Uložené a zdieľané obrázky <b>nebudú</b> odstránené pri vymazaní histórie aplikácie %1$s.
+
+
+
+ Téma vzhľadu
+
+ Svetlá
+
+ Tmavá
+
+ Podľa šetriča batérie
+
+ Podľa zariadenia
+
+
+
+ Táto stránka nepodporuje HTTPS
+
+
+ Ďalšie informácie
+ Toto nastavenie môžete zmeniť v časti Nastavenia > Súkromie a bezpečnosť > Bezpečnosť.]]>
+
+
+ Pripojenie nie je zabezpečené
+
+
+
+ Ak ste sa k tomuto serveru úspešne pripojili v minulosti, chyba môže byť dočasná.
+ ]]>
+
+
+ Niekto sa môže vydávať za tento server, a preto pokračovať by mohlo byť riskantné.
+
+ %1$s nedôveruje serveru %2$s , pretože vydavateľ jeho certifikátu je neznámy, certifikát je podpísaný sám sebou alebo server neposiela správne prechodné certifikáty.
+ ]]>
+
+
+
+ Zavrieť kartu
+
+
+
+ Máme ich! Zabránili sme tejto stránke špehovať vás. Kedykoľvek ťuknite na štít, aby ste videli, čo všetko blokujeme.
+
+
+ Zavrieť vyskakovacie okno
+
+
+
+ Ste chránení!
+
+ Tieto predvolené nastavenia ponúkajú silnú ochranu. Je však ľahké vyladiť nastavenia tak, aby vyhovovali vašim špecifickým potrebám.
+
+ Zavrieť
+
+
+ Ťuknutím sem všetko vyhodíte do koša – históriu, súbory cookie, všetko – a začnete odznova na novej karte.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Zavrieť
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Miniaplikácia vyhľadávania
+
+ !-- This is the title of promote search widget dialog. -->
+
+ História prehliadania bola vymazaná! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Začnite svoju reláciu súkromného prehliadania a my zablokujeme sledovacie prvky a iné zlé veci.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Necháme vás v súkromnom prehliadaní, ale nabudúce môžete začať rýchlejšie s miniaplikáciou %1$s na domovskej obrazovke.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Pridá miniaplikáciu na domovskú obrazovku
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Miniaplikácia bola pridaná na domovskú obrazovku
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-skr/strings.xml b/mobile/android/focus-android/app/src/main/res/values-skr/strings.xml
new file mode 100644
index 0000000000..37a03de51f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-skr/strings.xml
@@ -0,0 +1,1107 @@
+
+
+
+
+
+
+
+
+ منسوخ
+
+ ٹھیک ہے
+
+ محفوظ
+
+
+ ڳولو یا پتہ درج کرو
+
+
+ خودکار نجی براوزنگ۔\nبراوز کرو۔ مٹاؤ۔ دہراؤ۔
+
+
+ تہاݙی براؤزنگ تاریخ مٹا ڈتی ڳئی ہے۔
+ براؤزنگ تاریخ صاف تھی ڳئی
+
+
+ ٹیب دی براؤزنگ تاریخ مٹا ڈتی ڳئی ہے۔
+
+
+ %1$s کیتے ڳول
+
+
+ شیئر۔۔۔
+
+
+ سائٹ مسئلہ رپورٹ کرو
+
+
+ %1$s وچ کھولو
+
+
+ ۔۔۔ وچ کھولو
+
+
+ ہوم سکرین تے شامل کرو
+
+
+ شارٹ کٹاں وچ شامل کرو
+
+ شارٹ کٹاں کنوں ہٹاؤ
+
+
+ ترتیباں
+ تعارف
+ مدد
+
+ تہاݙے حقوق
+
+
+ ٹریکر بلاک تھئے
+
+
+ ایں کوں بند کرݨ نال سائٹ دے کجھ مسئلے ٹھیک تھی سڳدن
+
+
+ مواد روکݨ
+
+
+ کجھ سائٹاں کوں ٹھیک کرݨ کیتے بند کرو
+
+
+ %1$s ولوں تکڑا تھیا
+
+
+ شیئر بذریعہ
+
+ براؤزنگ تاریخ مٹاؤں؟
+
+ آپݨی براؤزنگ تاریخ کوں محفوظ طریقے نال مٹاوݨ کیتے ایں اطلاع نامے تے انگل پھیرو یا صاف کرو۔
+
+
+ آپݨی براؤزنگ تاریخ کوں محفوظ طریقے نال مٹاوݨ کیتے ایں اطلاع نامے تے انگل پھیرو یا انگل نال پونجھو۔
+
+ براؤزنگ تاریخ مٹاؤ
+
+
+ کھولو
+
+
+ مٹاؤ تے کھولو
+
+
+ مٹاؤ
+
+
+ براؤزنگ تاریخ مٹاؤ
+
+
+
+ مٹاؤ تے کھولو
+
+
+ مٹاؤ تے %1$s کھولو
+
+
+
+ فوکس وچ ڳولو
+
+ کلار وچ ڳولو
+
+ فوکس بیٹا وچ ڳولو
+
+ فوکس نائٹلی وچ ڳولو
+
+
+ %1$s تہاکوں کنٹرول ݙیندی ہے
+ ایں کوں نجی براؤزر وانگوں ورتو:
+
+ ایپ وچ لبھو تے براؤز کرو
+ ٹریکراں کوں بلاک کرو (یا ترتیباں دی تازہ کاری کر تے ٹریکراں کوں اجازت ݙیوو)۔
+ کوکیاں دے نال نال ڳولݨ تے براؤزنگ تاریخ کوں وی مٹاؤ
+
+
+%1$s موزلّا دی پیداوار ہے۔ ساݙی مہم ہک صحت مند کھلے انٹرنیٹ کوں فروعٰ ݙیوݨ ہے۔
+ ٻیا سِکھو
]]>
+
+
+ رازداری تے سلامتی
+
+
+ ٹریکنگ، کوکیاں، ڈیٹا چوائس
+
+
+ ڈیفالٹ مقرر کرو، خودکار مکمل
+
+
+
+
+ %1$s دا تعارف، مدد
+
+
+ بہتر ٹریکنگ حفاظت
+
+
+ ویب مواد
+
+
+ ایپاں تبدیل کرݨ
+
+
+ عمومی
+
+
+ ڈیفالٹ براؤزر، زبان
+
+
+ ڈیٹا مجموعہ تے ورتاوا
+
+ ڳولو
+
+
+ ڳولݨ تجویزاں گھنو
+
+
+ تساں جہڑا کجھ تساں ڳولݨ انجݨ دی پتہ پٹی وچ ٹائپ کریسو تہاکوں %1$s بھیڄ ݙیسی
+
+
+ ڈیفالٹ
+
+
+ ڳولݨ انجݨ
+
+
+ چالو
+
+
+ بند
+
+
+ یوآرایل خودکار تکمیل
+
+
+ بہترین سائٹاں کیتے
+
+
+ پتہ پٹی وچ ٤٥٠ کنوں ودھ مقبول یوآرایل آں کیتے %s خودکار تکمیل کوں فعال کرو۔
+
+
+ سائٹاں کیتے جہڑیاں تساں شامل کرو
+
+
+ آپݨی پسندیدہ یوآرایلاں کوں %s خودکار مکمل کرݨ کیتے فعال کرو
+
+
+ سائٹاں دا بندوبست کرو
+
+
+ سائٹاں دا بندوبست کرو
+
+
+ + کسٹم یوآرایل شامل کرو
+
+
+ تہاݙی خودکار تکمیل تندیر:
+
+
+ یوآرایل شامل کرو
+
+
+ کسٹم یوآرایل شامل کرو
+
+
+ کسٹم یوآرایل شامل کرو
+
+
+ خودکار تکمیل کیتے لنک شامل کرو
+
+
+ کوکیاں تے سائٹ ڈیٹا
+
+
+ ڈیٹا چوائس
+
+
+ من پسند یوآرایلاں ہٹاؤ
+
+
+ ٻیا سِکھو
+
+
+ من پسند خودکار مکمل یوآرایلاں دا بندوبست کرو تے شامل کرو۔
+
+
+ شامل کرݨ کیتے یوآرایل
+
+
+ یوآرایل پیسٹ یا درج کرو
+
+
+ مثال دے طور تے: mozilla.org
+
+
+ مثال: example.com
+
+
+ نویں من پسند یوآرایل شامل تھی ڳئی۔
+
+
+ ہٹاؤ
+
+
+ ہٹاؤ
+
+
+ درج تھئے یوآرایل دی ݙو واری پڑتال کرو۔
+
+ زبان
+
+ نظام ڈیفالٹ
+
+ رازداری
+
+ اشتہار ٹریکر کوں بلاک کرو
+ کجھ اشتہار سائٹ دے دورے کوں ٹریک کریندن، بھان٘ویں تساں مشہوریاں تے کلک وی نہ کرو۔
+ تجزیاتی ٹریکر بلاک کرو
+ انگل پھیرݨ تے سکرول کرݨ جیہاں سرگرمیاں کوݨ مِݨݨ، کٹھا کرݨ تے تجزیہ کرݨ کیتے ورتِیندے
+ سماجی ٹریکراں کوں بلاک کرو
+ تہاݙے دوریاں کوں ٹریک کرݨ تے شیئر بٹݨ وانگوں فعالیت ظاہر کرݨ کیتے سائٹاں تے ایمبیڈ تھئے
+ ٻئے مواد ٹریکر بلاک کرو
+ فعال کرݨ نال کجھ ورقے غیرمتوقع خراب تھی سڳدن
+ کوکیاں بلاک کرو
+
+
+ کو، شکریہ
+ صرف تریجھا فریق ٹریکر کوکیاں بلاک کرو
+ صرف تریجھا فریق کوکیاں بلاک کرو
+ کراس سائٹ کوکیاں بلاک کرو
+ جیا سوہݨا
+
+
+ ایپ دا جندرا کھولݨ کیتے فنگرپرنٹ ورتو
+
+
+ جے تساں شاٹ کٹ شامل کیتے ہن یا کوئی ویب سائٹ %s وچ پہلے ہی کھلی ہے تاں فنگر پرنٹ نال جندرہ کھولو
+
+
+ سٹلتھ
+
+
+ ایپاں سوئچ کرݨ ویلے ویب ورقیاں کوں لُکاؤ تے سکرین شاٹ گھنݨ بلاک کرو۔
+
+ سلامتی
+
+ کارکردگی
+ ویب فونٹ بلاک کرو
+
+
+ ایندے نال کجھ تصویراں یا آئیکان غائب تھی سڳدن
+
+ JavaScript بلاک کرو
+
+
+ ورقے تکھے تکھے لوڈ تھی سڳدن، پر کم خراب وی تھی سڳدے۔
+
+
+ %1$s کوں پہلوں مقرر براؤز بݨاؤ
+
+ موزلّا
+
+ ورتیل ڈیٹا پٹھو
+
+
+ ٻیا سِکھو
+
+
+ موزلّا صرف ایں کوں وٹلا کرݨ دی کوشش کریندےجہڑا ساکوں ہر کہیں کیتے %1$s فراہم کرݨ تے چنڳا بݨاوݨ دی لوڑ ہے۔
+
+
+ رازداری نوٹس
+
+
+ لائسنس ڄاݨکاری
+
+
+ لائبریریاں جہڑیاں اساں ورتیندوں
+
+
+ OSS | %s لائبریریاں
+
+
+ %1$s دے بارے
+
+
+ انسٹال تھئے ڳولݨ انجݨ
+
+
+ ڳولݨ انجݨ چݨو
+
+
+ ڈیفالٹ ڳولݨ انجݨ بحال کرو
+
+
+ + ہک ٻیا ڳولݨ انجݨ شامل کرو
+ ڳولݨ انجݨ ہٹاؤ
+ ہٹاؤ
+
+
+ ہک ٻیا ڳولݨ انجݨ شامل کرو
+
+ آپݨاں ترجیحی انجݨ چݨو:
+
+
+ ڳولݨ انجݨ شامل کرو
+
+ ڳولݨ انجݨ ناں
+ ورتݨ کیتے تند ڳولو
+ محفوظ
+
+
+ مثال: example.com/search/?q=%s
+
+ نواں ڳولݨ انجݨ شامل تھی ڳیا۔
+
+ ڳولݨ انجݨ دا ناں درج کرو
+
+ ہک انسٹال تھیا ڳولݨ انجݨ پہلے ہی ایہ ناں ورتیندا پئے۔
+
+ ڳولݨ تن٘د درج کرو
+
+ پڑتال کرو جو ڳولݨ تند مثال دے نمونے مطابق ہے
+
+
+ ان پٹ صاف کرو
+
+
+ فارغ کرو
+
+
+ براؤزنگ تاریخ مٹاؤ
+
+
+ %1$s ٹیب کھلے ہن
+
+
+ محفوظ کنکشن
+
+
+ لوڈ تھیندا پئے
+
+
+ ویب سائٹ لوڈ تھی ڳئی
+
+
+ ٻیاں آپشناں
+
+
+ ٻیاں آپشناں بٹݨ
+
+
+ اڳوں تے ون٘ڄو
+
+
+ ویب سائٹ ولدا لوڈ کرو
+
+
+ واپس ون٘ڄو
+
+
+ ویب سائٹ لوڈ کرݨ بند کرو
+
+
+ پچھلی ایپ تے واپس ون٘ڄو
+
+
+ بلاک تھئے ٹریکراں دی تعداد
+
+
+ ٹریکراں کوں بلاک کرو
+
+ تہاݙے حقوق
+
+ لنک ہک ٻئی ایپ وچ کھولو
+
+
+ ایں لنک کوں %2$s وچ کھولݨ کیتے تساں %1$s چھوڑ سڳدے ہو۔
+
+ ہک ایپ لبھو جہڑی لنک کوں کھول سڳے
+
+ تہاݙی ڈیوائس تے کوئی ایپ ایں کوں کھولݨ دی اہل کائنی۔ اہل ایپ کیتے تساں %1$s کوں چھوڑ تے %2$s کوں ڳولو۔
+
+ نجی براؤزنگ وچوں نکلوں؟
+
+
+ %1$s مکمل تھیا
+
+
+ کھولو
+
+
+ شارٹ کٹاں وچ شامل تھی ڳیا!
+
+ سرور کائنی لبھا
+
+
+ بند کرو
+
+
+
+ %1$s وچ ست بسم اللہ
+
+
+ تکھا۔ نجی۔ کوئی پریشانی کائنی۔
+
+
+ شروع کرو
+
+
+
+
+ %1$s ٻئے براؤزراں وانگوں کائنی
+
+
+ تساں ایپ بند کریسو تاں اساں وادھوں رازداری کیتے تہاݙی تاریخ صاف کر ݙیسوں۔
+
+
+
+ تساں جہڑے وی لنک کھولو آپݨے ڈیٹا دی حفاظت کیتے %1$s کوں آپݨا ڈیفالٹ براؤزر بݨاؤ۔
+
+
+ پہلوں مقرر براؤز تے طور تے سیٹ کرو
+
+
+ چھوڑو
+
+
+
+ آپݨی رازداری تکڑی کرو
+
+
+ نجی براؤزنگ کوں اڳلی سطح تے گھن ون٘ڄو۔ مشہوریاں تے ٻیا مواد، جہڑا تہاکوں سائٹاں تے ٹریک کر سڳدے تے ورقہ لوڈ تھیوں دے ویلے کوں پھسا سڳدن، کوں بلاک کرو۔
+
+
+ تہاݙا ڳولݨ۔ تہاݙا راہ
+
+
+ کوئی وکھری شئے ڳولیندے پئے ہو؟ ترتیباں وچ ٻیا ڈیفالٹ ڳولݨ انجݨ چُݨو۔
+
+
+ آپݨی ہوم سکرین تے شارٹ کٹاں شامل کرو
+
+
+ %1$s وچ تکھے تکھے آپݨی پسند دی سائٹ تے واپس آؤ۔ صرف %1$s مینو وچوں \"گھر سکرین تے شامل کرو\" کوں چُݨو۔
+
+
+ رازداری کوں عادت بݨاؤ
+
+
+ %1$s کوں آپݨاں ڈیفالٹ براؤزر مقرر کرو تے ڄݙݨ تساں ویب ورقے ٻئی ایپاں وچ کھولو تاں نجی براؤزنگ دے فائدے گھنو۔
+
+ ٹھیک ہے، میکوں سمجھ آڳئی ہے!
+ چھوڑو
+ اڳلا
+
+
+ -
+
+
+ شامل کرو
+
+ ڄیا
+
+
+ منسوخ
+
+ کو
+
+
+ بہترین ٹریکنگ حفاظت غیرفعال کرݨ نال شارٹ کٹ کھُلسی
+
+
+ نجی براؤزنگ سیشن
+
+
+ اطلاعواں تہاکوں %1$s مجلس ہک انگل پھیرݨ نال مٹاوݨ دی اجازت ݙیندیاں ہن۔ تہاکوں ایپ کھولݨ یا ایہ ݙیکھݨ دی لوڑ کائنی جو تہاݙے براؤزر وچ کیا چلدا پئے۔
+
+
+ براؤزنگ تاریخ مٹاؤ
+
+
+ فائرفوکس ڈاؤن لوڈ کرو
+
+
+
+
+
+ موزلّا عوامی لائسنس تے ٻئے کھلے ماخذ لائسنساں دیاں شرطاں دے تحت تہاݙے کیتے %1$s دستیاب بݨایا ڳئے۔]]>
+
+
+ اتھ لبھ سڳدی ہے۔]]>
+
+
+ لائسنس دے تحت وادھوں ماخذ کوڈ دستیاب ہے۔]]>
+
+
+ جی این یو عمومی عوامی لائسنس وی3 دے تحت انج تے آزاد کماں دے طور تے ڈسکنکٹ، انک۔ ولوں فراہم تھیاں بلاک تندیراں کوں وی ورتیندے، تے اِتھ دستیاب ہے۔]]>
+
+
+ ورتݨ ناں
+ پاس ورڈ
+ صاف کرو
+
+
+
+ محفوظ کنکشن
+ غیرمحفوظ کنکشن
+
+
+ ایندے ولوں پڑتال تھئی: %1$s
+
+
+ سائٹ سلامتی
+
+ یوآرایل پہلے ہی موجود ہے
+
+
+ ورقے وچ لبھو
+
+
+ ورقے وچ لبھو
+
+
+ %1$d/%2$d
+
+ %2$d وچوں %1$d
+
+
+ اڳلا نتیجہ لبھو
+
+ پچھلا نتیجہ لبھو
+
+ ورقے وچ لبھݨ کوں فارغ کرو
+
+
+ ڈیسک ٹاپ سائٹ دی ارداس کرو
+
+
+ ڈیسک ٹاپ سائٹ
+
+
+ یوآرایل نقل تھی ڳیا
+
+
+ تخلیق کار اوزار
+
+
+ لنک ایپاں وچ کھولو
+
+
+ ودھایا
+
+
+ سائٹ اجازتاں
+
+
+ کوکی بینر گھٹاوݨ
+
+
+ چالو
+
+
+ بند
+
+
+ کوکی بینر گھٹاوݨ
+
+
+ جہڑے ویلے ممکن ہووے، کوکی ارداساں کوں آپݨے آپ مسترد کرتے گھٹ بینر ݙکھاؤ۔
+
+ -->
+ کوکی بینر گھٹاوݨ
+
+
+ ایں سائٹ کیتے چالو کرو
+
+
+ سائٹ فی الحال سہارا تھئی کائنی
+
+
+ ایں سائٹ کیتے بند کرو
+
+
+ کوکی بینر گھٹاوݨ
+
+
+ ایں سائٹ کیتے بند کرو
+
+
+ ایں سائٹ کیتے چالو کرو
+
+
+ %1$s کیتے کوکی بینر گھٹاوݨ چالو کروں؟
+
+
+ %1$s کیتے کوکی بینر گھٹاوݨ بند کروں؟
+
+
+ %1$s ایں سائٹ دیاں کوکیاں صاف کریسی تے ورقہ تازہ کریسی۔ساریاں کوکیاں صاف کرݨ نال تساں سائن آوٹ تھی سڳدے ہو یا تہاݙی خریداری ریڑھی خالی تھی ویسی۔
+
+
+ %1$s آپݨے آپ کوکی ارداساں کوں مسترد کرݨ دی کوشش کر سڳدے۔
+
+
+ ایہ سائٹ کوکی بینر گھٹاوݨ ولوں حمایت یافتہ کائنی۔ بھلا تساں ساݙی ٹیم کوں ایں ویب سائٹ تے نظرثانی کرݨ تے مستقبل وچ سپورٹ شامل کرݨ کیتے ارداس کرݨ پسند کریسو؟
+
+
+ منسوخ
+
+
+ سپورٹ کیتے ارداس کرو
+
+
+ سپورٹ سائٹ دی ارداس جمع کرا ݙتی ڳئی ہے۔
+
+
+ سپورٹ سائٹ دی ارداس جمع کرا ݙتی ڳئی ہے۔
+
+
+
+ %1$s پریشان کن کوکی بینراں کوں برخواست کرݨ دیاں کوکی ارداساں کوں مسترد کرݨ دی کوشش کریندے۔ \n\n %2$s وچ کوکی بینر ترجیحاں دا بندوبست کرو۔
+
+
+ ترتیباں
+
+
+ آٹو پلے
+
+
+ ایں کوں اجازت ݙیوݨ کیتے:
+
+
+ ١۔ اینڈرائیڈ ترتیباں تے ون٘ڄو
+
+
+ اجازتاں تے دباؤ]]>
+
+
+ ترتیباں تے ون٘ڄو
+
+
+ %1$s کوں چالو کرݨ کیتے ٹوگل کرو]]>
+
+
+ کیمرہ
+
+
+ مائیکروفون
+
+
+ مقام
+
+
+ اطلاع نامہ
+
+
+ DRM دے زیرانتظام مواد
+
+
+ اجازت کیتے پُچھو
+
+
+ بلاک تھی ڳیا
+
+
+ اجازت ݙتے ہوئے
+
+
+ اینڈرائیڈ ولوں بلاک تھئے
+
+
+ آڈیو تے وڈیو دی اجازت ݙیوو
+
+
+ صرف آڈیو بلاک کرو
+
+
+ سفارش تھئے ہوئے
+
+
+ آڈیو تے وڈیو بلاک کرو
+
+
+ مطالعہ
+
+
+ فائرفوکس وقتاً فوقتاً مطالعہ انسٹال کرسڳدے تے چلا سڳدے۔
+
+
+ ٻیا سِکھو
+
+
+ تبدیلیاں لاگو کرݨ کیتے ایپ چھوڑ ݙتی ویسی
+
+
+ ہٹاؤ
+
+
+ فعال
+
+
+ مکمل تھی ڳیا
+
+
+ یوایس بی/وائی فائی دے ذریعے بعید ٹھیک کاری
+
+
+ جندرہ کھولو
+
+
+ آپݨی فنگرپرنٹ ورتݨ نال تصدیق کرو
+
+
+ آپݨی رواں ایپ مجلس جاری رکھݨ کیتے تساں آپݨے فنگرپرنٹ ورت سڳدے ہو۔
+
+
+ لنک نویں سیشن وچ کھولو
+
+
+ فنگر پرنٹ آئیکان
+
+
+ فنگرپرنٹ دی سُن٘ڄاݨ کائنی تھئی۔ ولدا کوشش کرو۔
+
+
+ انگل ݙاڈھی تکھی ہلی ہے۔ ولدا کوشش کرو۔
+
+
+ ڳولݨ تجویزاں ݙکھاؤں؟
+
+
+ تجویزاں گھنݨ کیتے، %1$s کوں، تساں جہڑا کجھ تساں ڳولݨ انجݨ دی پتہ پٹی وچ ٹائپ کریندے ہو، بھیڄݨ دی ضرورت ہے۔
+
+
+ کو
+
+
+ جیا
+
+
+ کجھ ڳولݨ انجݨ تجویزاں کائنی ݙکھا سڳدے۔
+
+
+ فارغ کرو
+
+
+
+
+ بھلا سائٹ غیر متوقع سلوک کریندی پئی ہے؟\n
+ ٹریکنگ حفاظت بند کر تے ݙیکھو
+
+
+ گھر سکرین وچ شامل کرو]]>
+
+
+ سارے لنک %1$s وچ کھولو \n
+ %1$s کوں ڈیفالٹ براؤزر مقرر کرو
+
+
+
+ جہہڑیاں سائٹاں تساں ودھ ورتیاں ہن انہاں کیتے خودکار تکمیل یوآرایل آں\n
+ پتہ پٹی تے کہیں وی یوآرایل تے لمبا دباؤ
+
+
+
+ لنک نویں ٹیب وچ کھولو\n
+ ورقے تے کہیں لنک کوں لمبا دباؤ
+
+
+
+ موہری سکرین تے تجویزاں بند کرو
+
+
+ نواں ٹیب کھل ڳیا
+
+
+ سوئچ کرو
+
+
+ پوری سکرین موڈ وچ داخل تھیندا پئے
+
+
+ فوری طور تے نویں ٹیب وچ لنک تے سوئچ کرو
+
+
+ ممکنہ خطرناک تے فریبی سائٹاں بلاک کرو
+
+ گمراہ کن تے حملہ کرݨ آلی سائٹاں، میلویئر سائٹاں، تے اݨ بھاندے سافٹ ویئر سائٹاں کوں بلاک کرو۔
+
+ ایچ ٹی ٹی پی ایس ــ صرف موڈ
+
+
+ خودکار طور تے ودھدی ہوئی حفاظت کیتے ایچ ٹی ٹی پی ایس خفیہ کاری پروٹوکول ورتندے ہوئے سائٹاں کوں کنکٹ کرݨ دی کوشش کریندے۔
+
+
+ استثنيات
+
+ انہاں ویب سائٹاں کیتے تساں مواد بلاک کرݨ غیرفعال کر ݙتا ہے۔
+
+ ہٹاؤ
+
+
+ ساریاں ویب سائٹاں ہٹاؤ
+
+
+ کوکیاں بلاک کرو
+
+
+ بھلا تساں کوکیاں کوں بلاک کرݨ چاہندے ہو؟
+
+
+ ٹیب تباہ تھی ڳیا
+
+ افسوس، ساکوں ایں ٹیب نال ہک مسئلہ ہے۔
+
+ ہک نجی براؤزر دے طور تے، اساں ایں ٹیب کوں کݙاہیں وی محفوظ یا بحال نسّے کر سڳدے۔
+
+ ٹیب بند کرو
+
+
+ موزلّا کوں کریش رپوٹ بھیڄو
+
+
+
+
+ ٹریکرز %s کنوں بلاک ہن۔
+
+ مواد
+
+
+ اشتہار
+
+ سماجی
+
+ تجزیہ
+
+ بہتر ٹریکنگ حفاظت
+
+ ایں سائٹ کیتے حفاظتاں بند ہن۔
+
+ ایں سائٹ کیتے حفاظتاں چالو ہن۔
+
+ کنکشن محفوظ ہے۔
+
+ کنکشن محفوظ کائنی۔
+
+
+ بلاک کرݨ کیتے ٹریکر تے سکرپٹ
+
+
+ واپس ون٘ڄو
+
+
+
+ ہٹاؤ
+
+ ناں وٹاؤ
+
+ ناں وٹاؤ
+
+ شارٹ کٹ ناں
+
+
+ تساں جہڑے ویلے %1$s تاریخ مٹیسو تاں محفوظ تے شیئر تھیاں تصویراں <b>کائناں مٹسن</b>
+
+
+
+ تھیم
+
+ پھکا
+
+ شوخ
+
+ بیٹری سیور نال سیٹ تھیا
+
+ ڈیوائس تھیم تے عمل کرو
+
+
+ ایہ سائٹ ایچ ٹی ٹی پی ایس کوں سہارا کائنی ݙیندی
+
+
+ ٻیا سکھو
+ ایہ ترتیباں وٹاؤ، ترتیباں > رازداری & سلامتی > سلامتی۔]]>
+
+
+ ناقابل بھروسا کنکشن
+
+
+
+ جے تساں پچھوکڑ وِچ ایں سرور نال کامیابی دے نال جڑ چکے او، تاں غلطی عارضی تھی سڳدی ہے، تے تساں بعد اِچ دوبارہ کوشش کر سڳدے او۔
+ ]]>
+
+
+ تھی سڳدا ہے جو کوئی سائٹ دی نقل بݨاوݨ دی کوشش کریندا پیا ہووے تے جاری رکھݨ نال خدشہ تھی سڳدے۔
+
+ %1$s، ایں %2$s تے بھروسہ کائنی کریندا کیوں جو سرٹیفکیٹ جاری کرݨ آلا نامعلوم ہے، سرٹیفکیٹ تے خود آپ دستخط کیتے ہوئے ہن یا سرور صحیح درمیانی سرٹیفیکیٹ کائنی پٹھیندا پیا۔ ]]>
+
+
+
+ ٹیب بند کرو
+
+
+
+ ایں کوں سمجھ گھدے! اساں ایں سائٹ کوں تہاݙی جاسوسی کرݨ کنوں روک ݙتے۔ جہڑی شئے اساں روکی ہے اوں کوں ݙیکھݨ کیتے کہیں وی ویلے شیلڈ تے انگل لاؤ۔
+
+
+ پوپ اپ بند کرو
+
+
+
+ تساں محفوظ ہو!
+
+ ایہ ڈیفالٹ ترتیباں ݙاڈھی سخت حفاظت کریندیاں ہن۔ پر تہاݙیاں خاص ضروریات کوں پورا کرݨ کیتے ترتیباں کوں موافق کرݨ سوکھا ہے۔
+
+ فارغ کرو
+
+
+ تاریخ، کوکیاں تے سب کجھ ــــ ایں کوں ردی دی ٹوکری وچ سٹݨ کیتے انگل لاؤ ـــ تے نویں ٹیب تے تازہ شروع کرو۔
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ بند کرو
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ ویجٹ ڳولو
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ براؤزنگ تاریخ صاف تھی ڳئی! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ آپݨاں نجی براؤزنگ سیشن شروع کرو، اساں تہاݙے ون٘ڄݨ سیتی ٹریکراں تے ٻیاں خراب شئیاں کوں بلاک کر ݙیسوں۔
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ اساں تہاکوں تہاݙی نجی براؤزنگ تے چھوڑ ݙیسوں، پر تساں اڳلی واری آپݨی ہوم سکرین تے %1$s ویجٹ نال تکھیرا شروع کرو۔
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ ہوم سکرین تے ویجٹ شامل کرو
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ ہوم سکرین تے ویجٹ شامل تھی ڳیا
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sl/strings.xml b/mobile/android/focus-android/app/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000000..6c3f7349e3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sl/strings.xml
@@ -0,0 +1,1123 @@
+
+
+
+
+
+
+
+
+ Prekliči
+
+ V redu
+
+ Shrani
+
+
+ Iskanje ali naslov strani
+
+ Samodejno zasebno brskanje.\nBrskajte. Izbrišite. Ponovite.
+
+
+ Vaša zgodovina brskanja je bila izbrisana.
+ Zgodovina brskanja izbrisana
+
+
+ Zgodovina brskanja tega zavihka je bila izbrisana.
+
+
+ Išči %1$s
+
+
+ Deli …
+
+
+ Prijavi napako strani
+
+
+ Odpri v %1$s
+
+
+ Odpri v …
+
+
+ Dodaj na domač zaslon
+
+
+ Dodaj med bližnjice
+
+ Odstrani iz bližnjic
+
+
+ Nastavitve
+ Predstavitev
+ Pomoč
+ Vaše pravice
+
+
+ Zavrnjeni sledilci
+
+
+ Z izklopom zavračanja lahko odpravite nekatere težave s stranjo
+
+
+ Zavračanje vsebine
+
+ Izklopite za pravilno delovanje nekaterih strani
+
+
+ Omogoča: %1$s
+
+
+ Deli preko
+
+ Izbrišem zgodovino brskanja?
+ Tapnite ali odstranite to obvestilo za varen izbris zgodovine brskanja.
+
+
+ Tapnite to obvestilo ali ga podrsajte vstran za varen izbris zgodovine brskanja.
+
+ Počisti zgodovino brskanja
+
+
+ Odpri
+
+
+ Izbriši in odpri
+
+
+ Izbriši
+
+
+ Počisti zgodovino brskanja
+
+
+
+ Izbriši in odpri
+
+
+ Izbriši in odpri %1$s
+
+
+
+ Išči v Focusu
+
+ Išči v Klaru
+
+ Išči v Focusu Beta
+
+ Išči v Focusu Nightly
+
+
+ %1$s daje nadzor v vaše roke.
+Uporabljajte ga kot zaseben brskalnik:
+
+ Iščite in brskajte naravnost iz aplikacije
+ Zavračajte sledilce (ali spremenite nastavitve zavračanja sledilcev)
+ Brišite piškotke ter zgodovino strani in iskanja
+
+
+%1$s razvija Mozilla. Naše poslanstvo je spodbujati zdrav in odprt internet.
+Več o tem
]]>
+
+
+ Zasebnost in varnost
+
+
+ Sledenje, piškotki, podatkovne možnosti
+
+
+ Nastavi kot privzeto, samodejno dopolni
+
+
+
+
+ O %1$su, pomoč
+
+
+ Izboljšana zaščita pred sledenjem
+
+
+ Spletna vsebina
+
+
+ Preklapljanje aplikacij
+
+
+ Splošno
+
+
+ Privzeti brskalnik, jezik
+
+
+ Zbiranje in uporaba podatkov
+
+ Iskanje
+
+
+ Prejemaj predloge iskanja
+
+ %1$s bo natipkano v naslovno vrstico poslal iskalniku
+
+
+ Privzet
+
+
+ Iskalnik
+
+
+ Vključeno
+
+
+ Izklopljeno
+
+
+ Samodejno dopolnjevanje URL
+
+
+ Za glavne strani
+
+
+ Omogočite, če želite, da %s v naslovni vrstici samodejno dopolni preko 450 pogostih spletnih naslovov.
+
+
+ Za strani, ki jih dodate
+
+
+ Omogočite, če želite, da %s samodejno dopolni vaše priljubljene naslove.
+
+
+ Upravljanje strani
+
+
+ Upravljanje strani
+
+
+ + Dodaj URL po meri
+
+
+ Vaš seznam za samodokončanje:
+
+
+ Dodaj spletni naslov
+
+
+ Dodaj URL po meri
+
+
+ Dodaj spletni naslov po meri
+
+
+ Dodaj povezavo za samodejno dopolnjevanje
+
+
+ Piškotki in podatki strani
+
+
+ Podatkovne možnosti
+
+
+ Odstrani URL po meri
+
+
+ Več o tem
+
+
+ Dodajte in upravljajte z URL-ji po meri za samodejno dokončevanje.
+
+
+ URL za dodajanje
+
+
+ Prilepite ali vpišite URL
+
+
+ Primer: mozilla.org
+
+
+ Primer: example.com
+
+
+ Nov URL po meri dodan.
+
+
+ Odstrani
+
+
+ Odstrani
+
+
+ Preverite vneseni URL.
+
+ Jezik
+
+ Privzet v sistemu
+
+ Zasebnost
+ Zavračaj sledilce oglasov
+ Nekateri oglasi beležijo obiske strani, tudi če ne klikate nanje
+ Zavračaj sledilce analitike
+ V uporabi za zbiranje, analiziranje in merjenje dejanj, kot je pritiskanje in drsenje po strani
+ Zavračaj sledilce družbenih omrežij
+ Vgrajeni na straneh za sledenje vašim obiskom in za prikaz možnosti, kot so gumbi za deljenje
+ Zavračaj druge vsebinske sledilce
+ Nastavitev lahko povzroči nepravilno delovanje nekaterih strani
+ Zavračaj piškotke
+
+
+ Ne, hvala
+ Zavračaj samo sledilne piškotke tretjih strani
+ Zavračaj samo piškotke tretjih strani
+
+ Zavračaj spletne piškotke
+ Da!
+
+
+ Uporabljaj prstni odtis za odklepanje aplikacije
+
+
+ Odklenite s prstnim odtisom, če ste dodali bližnjice ali če je spletno mesto že odprto v %s.
+
+
+ Skrivni način
+
+ Skrij strani pri preklapljanju med aplikacijami in prepreči zajemanje posnetkov zaslona.
+
+ Varnost
+
+ Učinkovitost
+ Zavračaj spletne pisave
+
+ Lahko povzroči izginotje ikon ali slik
+
+ Onemogoči JavaScript
+
+ Strani se bodo lahko nalagale hitreje, vendar morda ne bodo delovale pravilno
+
+
+ Nastavi %1$s kot privzeti brskalnik
+
+ Mozilla
+ Pošiljaj podatke o uporabi
+
+
+ Več o tem
+
+
+ Mozilla se trudi, da zbira samo podatke, ki jih potrebuje za razvoj in izboljševanje %1$sa.
+
+
+ Obvestilo o zasebnosti
+
+
+ Podatki o dovoljenjih
+
+
+ Knjižnice, ki jih uporabljamo
+
+
+ %s | Knjižnice OSS
+
+
+ O %1$su
+
+
+ Nameščeni iskalniki
+
+
+ Izberi iskalnik
+
+
+ Ponastavi privzete iskalnike
+
+
+ + Dodaj drug iskalnik
+ Odstrani iskalnike
+ Odstrani
+
+
+ Dodaj drug iskalnik
+
+ Izberite želeni iskalnik:
+
+
+ Dodaj iskalnik
+
+ Ime iskalnika
+ Iskalni niz za uporabo
+ Shrani
+
+
+ Primer: example.com/search/?q=%s
+
+ Nov iskalnik dodan.
+
+ Vnesite ime iskalnika
+ Nameščeni iskalnik že uporablja to ime.
+
+ Vnesite iskalni niz
+
+ Prepričajte se, da se iskalni niz ujema z obliko primera
+
+
+ Počisti vnos
+
+
+ Opusti
+
+
+ Počisti zgodovino brskanja
+
+
+ Odprtih zavihkov: %1$s
+
+
+ Varna povezava
+
+
+ Nalaganje
+
+
+ Spletna stran naložena
+
+
+ Več možnosti
+
+
+ Gumb za več možnosti
+
+
+ Krmari naprej
+
+
+ Ponovno naloži stran
+
+
+ Krmari nazaj
+
+
+ Prekini nalaganje strani
+
+
+ Nazaj na prejšnjo aplikacijo
+
+
+ Število zavrnjenih sledilcev
+
+
+ Zavračaj sledilce
+
+ Vaše pravice
+
+ Odpri povezavo v drugi aplikaciji
+
+ Lahko zapustite %1$s in to povezavo odprete v %2$s.
+
+ Poišči aplikacijo, ki lahko odpre povezavo
+
+ Nobena aplikacija na napravi ne more odpreti te povezave. Lahko zapustite %1$s in v %2$s poiščete aplikacijo, ki jo lahko.
+
+ Končam zasebno brskanje?
+
+
+ Datoteka %1$s končana
+
+
+ Odpri
+
+
+
+
+
+
+
+
+
+
+ Dodano med bližnjice!
+
+ Strežnika ni mogoče najti
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zapri
+
+
+
+
+ Dobrodošli v %su
+
+
+ Hitro. Zasebno. Brez motenj.
+
+
+ Začnite
+
+
+
+
+ %1$s ni kot drugi brskalniki
+
+
+ Za dodatno zasebnost izbrišemo vašo zgodovino, ko zaprete aplikacijo.
+
+
+
+ Nastavite %1$s kot privzeti brskalnik, ki naj ščiti vaše podatke vsakič, ko odprete povezavo.
+
+
+ Nastavi kot privzeti brskalnik
+
+
+ Preskoči
+
+
+
+ Okrepite svojo zasebnost
+
+ Zasebno brskanje na višjem nivoju. Zavračajte oglase in drugo vsebino, ki vam lahko sledi prek strani, in pohitrite svoje brskanje.
+
+
+ Vaše iskanje, na vaš način
+
+ Iščete nekaj drugačnega? V nastavitvah izberite drug privzeti iskalnik.
+
+
+ Dodajte bližnjice na domač zaslon
+
+ Hitro se vrnite na svoje priljubljene strani v %1$su. Preprosto izberite \"Dodaj na domač zaslon\" v meniju %1$s.
+
+
+ Naj zasebnost postane navada
+
+ Nastavite %1$s kot privzet brskalnik in uživajte prednosti zasebnega brskanja, ko odprete spletne strani v drugih aplikacijah.
+
+ V redu, razumem!
+ Preskoči
+ Naprej
+
+
+ -
+
+
+ Dodaj
+
+
+ DA
+
+
+ Prekliči
+
+
+ NE
+
+
+ Bližnjica se bo odprla z onemogočeno izboljšano zaščito pred sledenjem
+
+
+ Seja zasebnega brskanja
+
+
+ Obvestila vam omogočajo izbrisati %1$sovo sejo z enim dotikom. Aplikacije vam ni potrebno niti odpreti niti videti, kaj je v brskalniku odprto.
+
+
+ Počisti zgodovino brskanja
+
+
+ Prenesite Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License in drugih odprtokodnih licenc.]]>
+
+
+ tukaj.]]>
+
+
+ licencami.]]>
+
+
+ GNU General Public License v3 tukaj .]]>
+
+
+ Uporabniško ime
+ Geslo
+ Počisti
+
+
+
+ Varna povezava
+ Povezava ni varna
+
+ Overil: %1$s
+
+
+ Varnost strani
+ URL že obstaja
+
+
+ Najdi na strani
+
+
+ Najdi na strani
+
+
+ %1$d/%2$d
+
+ %1$d od %2$d
+
+
+ Najdi naslednji zadetek
+
+ Najdi prejšnji zadetek
+
+ Končaj iskanje na strani
+
+
+
+
+ Zahtevaj stran za namizja
+
+
+ Stran za namizja
+
+
+ Spletni naslov kopiran
+
+
+ Orodja za razvijalce
+
+
+ Odpiraj povezave v aplikacijah
+
+
+ Napredno
+
+
+ Dovoljenja strani
+
+
+ Zmanjšanje števila pasic s piškotki
+
+
+ Vključeno
+
+
+ Izključeno
+
+
+ Zmanjšanje števila pasic s piškotki
+
+
+ Zmanjšajte število pasic s samodejnim zavračanjem zahtev za nastavljanje piškotkov, kadar je to mogoče.
+
+ -->
+ Zmanjšanje števila pasic s piškotki
+
+
+ VKLJUČENO na tem spletnem mestu
+
+
+ Spletno mesto trenutno ni podprto
+
+
+ IZKLJUČENO na tem spletnem mestu
+
+
+ Zmanjšanje števila pasic s piškotki
+
+
+ IZKLJUČENO na tem spletnem mestu
+
+
+ VKLJUČENO na tem spletnem mestu
+
+
+ Vključim zmanjšanje števila pasic s piškotki za %1$s?
+
+
+ Izključim zmanjšanje števila pasic s piškotki za %1$s?
+
+
+ %1$s bo počistil piškotke tega spletnega mesta in osvežil stran. Če počistite vse piškotke, boste morda odjavljeni ali se bo izpraznila vaša nakupovalna košarica.
+
+
+ %1$s lahko poskusi samodejno zavrniti zahteve za shranjevanje piškotkov.
+
+
+ Zmanjšanje pasic s piškotki zaenkrat ne podpira tega spletnega mesta. Ali želite poslati zahtevek, naj spletno mesto pregleda naša ekipa in doda podporo v prihodnjih različicah?
+
+
+ Prekliči
+
+
+ Zaprosi za podporo
+
+
+ Zahtevek spletnemu mestu za podporo poslan.
+
+
+ Zahtevek spletnemu mestu za podporo poslan.
+
+
+
+ %1$s poskuša samodejno zavrniti zahteve za nastavljanje piškotkov, da ne prikazuje nadležnih pasic s piškotki.\n\nNastavitve pasic s piškotki lahko spremenite v %2$s.
+
+
+ nastavitvah
+
+
+ Samodejno predvajanje
+
+
+ Če želite omogočiti:
+
+
+ 1. Pojdite v Nastavitve Androida
+
+
+ Dovoljenja]]>
+
+
+ Odpri nastavitve
+
+
+ %1$s na VKLOPLJENO]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Lokacija
+
+
+ Obvestila
+
+
+ Vsebina, zaščitena z DRM
+
+
+ Vprašaj za dovoljenje
+
+
+ Zavrnjeno
+
+
+ Dovoljeno
+
+
+ Zavrnil Android
+
+
+ Dovoli zvok in video
+
+
+ Zavrni samo zvok
+
+
+ Priporočeno
+
+
+ Zavrni zvok in video
+
+
+ Raziskave
+
+
+ Firefox lahko občasno namesti in zažene raziskave.
+
+
+ Več o tem
+
+
+ Aplikacija se bo zaprla za uveljavitev sprememb
+
+
+ Odstrani
+
+
+ Dejavna
+
+
+ Končana
+
+
+ Oddaljeno razhroščevanje preko USB/Wi-Fi
+
+
+ Odkleni
+
+
+ Potrdite s prstnim odtisom
+
+
+ Za nadaljevanje trenutne seje aplikacije lahko uporabite svoj prstni odtis.
+
+
+ Odpri povezavo v novi seji
+
+
+ Ikona prstnega odtisa
+
+
+ Prstni odtis ni prepoznan. Poskusite ponovno.
+
+
+ Prst se je premaknil prehitro. Poskusite znova.
+
+
+ Prikaži predloge iskanja?
+
+
+ Da pridobi predloge iskanja, mora %1$s besede, ki jih natipkate v naslovno vrstico, pošiljati iskalniku.
+
+
+ Ne
+
+
+ Da
+
+
+ Nekateri iskalniki ne morejo prikazovati predlogov.
+
+
+ V redu
+
+
+
+
+ Ali stran deluje nepravilno?\n Poskusite izklopiti zaščito pred sledenjem
+
+
+ Dodaj na domači zaslon]]>
+
+
+ Odpiraj vse povezave v %1$su\n Nastavite %1$s za privzeti brskalnik
+
+
+ Samodejno dokončajte spletne naslove za strani, ki jih najpogosteje obiskujete\n Pridržite prst na kateremkoli spletnem naslovu v naslovni vrstici
+
+
+ Odprite povezavo v novem zavihku\n Pridržite prst na katerikoli povezavi na strani
+
+
+ Izključi namige na začetnem zaslonu
+
+
+ Odprt nov zavihek
+
+
+ Preklopi
+
+
+ Vstopanje v celozaslonski način
+
+
+ Takoj preklopi na povezavo v novem zavihku
+
+
+ Zavrni morebitne nevarne in zavajajoče strani
+
+ Zavračaj znane zavajajoče in napadalne strani ter strani z zlonamerno ali neželeno programsko opremo.
+
+
+ Način "samo HTTPS"
+
+
+ Za večjo varnost poskuša samodejno vzpostaviti povezavo s šifrirnim protokolom HTTPS.
+
+
+ Izjeme
+
+ Za ta spletna mesta ste onemogočili zavračanje vsebine.
+
+ Odstrani
+
+ Odstrani vse spletne strani
+
+
+ Zavračaj piškotke
+
+
+ Ali želite zavračati piškotke?
+
+
+ Zavihek se je sesul
+
+ Oprostite. S tem zavihkom imamo težavo.
+
+ Kot zaseben brskalnik nikoli ne shranimo vaših zavihkov in ga ne moremo obnoviti.
+
+ Zapri zavihek
+
+
+
+
+
+ Pošlji poročilo o sesutju Mozilli
+
+
+
+
+ Zavrnjeni sledilci od %s
+
+ Vsebina
+
+ Oglasi
+
+ Družbeni
+
+ Analitika
+
+ Izboljšana zaščita pred sledenjem
+
+ Zaščite za to spletno mesto so IZKLJUČENE
+
+ Zaščite za to spletno mesto so VKLJUČENE
+
+ Povezava je varna
+
+ Povezava ni varna
+
+
+ Sledilci in skripte za zavračanje
+
+
+ Nazaj
+
+
+
+ Odstrani
+
+
+ Preimenuj
+
+ Preimenuj
+
+
+ Ime bližnjice
+
+
+ Ob izbrisu zgodovine %1$sa shranjene in deljene slike <b>ne bodo</b> izbrisane
+
+
+
+ Tema
+
+ Svetla
+
+ Temna
+
+ Nastavi ohranjevalnik baterije
+
+ Sledi temi naprave
+
+
+
+ To spletno mesto ne podpira HTTPS
+
+
+ Več o tem
+ To lahko spremenite v Nastavitve > Zasebnost in varnost > Varnost.]]>
+
+
+ Povezava ni varna
+
+
+
+ Če ste se v preteklosti že uspešno povezali na ta strežnik, je napaka morda samo začasna.
+ ]]>
+
+
+ Nekdo lahko poskuša oponašati to spletno mesto, zato je nadaljevanje lahko tvegano.
+
+ %1$s ne zaupa spletnemu mestu %2$s , ker izdajatelj njegovega digitalnega potrdila ni znan, ker je potrdilo samopodpisano ali pa strežnik ne pošilja pravih vmesnih digitalnih potrdil.
+ ]]>
+
+
+
+ Zapri zavihek
+
+
+
+ Imamo jih! Temu spletnemu mestu smo preprečili vohunjenje za vami. Če želite videti, kaj blokiramo, tapnite ščit.
+
+
+ Zapri pojavno okno
+
+
+
+ Zaščiteni ste!
+
+ Te privzete nastavitve nudijo močno zaščito. Preprosto pa jih lahko prilagodite tako, da ustrezajo vašim potrebam.
+
+ Zapri
+
+
+ Tapnite tukaj, da vse vržete v smeti – zgodovino, piškotke, vse – in začnete na novo v novem zavihku.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Zapri
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Pripomoček za iskanje
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Zgodovina brskanja izbrisana! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Začnite zasebno sejo brskanja in sproti bomo zavračali sledilce ter druge škodljive elemente.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Prepustili vas bomo zasebnemu brskanju, vendar lahko naslednjič hitreje začnete s pripomočkom %1$s na domačem zaslonu.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Dodaj pripomoček na začetni zaslon
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Pripomoček dodan na začetni zaslon
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sn/strings.xml b/mobile/android/focus-android/app/src/main/res/values-sn/strings.xml
new file mode 100644
index 0000000000..e9a8b41537
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sn/strings.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+ Kanzura
+ Zvakanaka
+
+ Sevha
+
+ Tsvaga kana kunyora kero
+
+ Tsvaga zvakawanzika.\nTsvaga. Dzima. Dzokorora.
+
+ Zvawaiita paindaneti zvadzimwa.
+
+ Zvawaiita paindaneti zvadzimwa.
+
+ Tsvaga %1$s
+
+ Paradzira…
+
+ Mhan\'ara Mhosho Pano
+
+ Vhurira mu %1$s
+
+ Vhurira mu…
+
+ Isa pa peji Repekutanga
+
+ Gadziriso
+ Nezvedu
+ Rubatsiro
+ Kodzero dzako
+
+ Zvinotevera zvavharwa
+
+ Kuita izvi zvingagadzirisa zvimwe zvinetso zvepasaiti
+
+ Kubhuloka kondendi
+ Bvisa kugadzirisa dzimwe nzvimbo
+
+ Zvasimbiswa na%1$s
+
+ Govera uchishandisa
+
+ Dzima nhoroondo yezvakatsvakwa
+
+ Vhura
+
+ Dzima neku Vhura
+
+ Dzima
+
+ Dzima zvawaita paindaneti
+
+
+ Dzima wovhura
+
+ Dzima wovhura %1$s
+
+ Kuchengeteka ne Puraivhesi
+
+ Kuchengeta, makuki, kusarudzwa kwe data
+
+ Ita zvisizvo, zvakakwana
+
+ Nezvedu %1$s, rubatsiro
+
+ Zviri paindaneti
+
+ Kuchinja maapurikesheni
+
+ Zvechigarire
+
+ Kutorwa kweData nekushandiswa kwaro
+
+ Tsvaga
+
+ Tsvaga mazano ekutsvaga
+ %1$s ichatumira zvaunonyora mu adhiresi bha yako ye injini yekutsvaga
+
+ Zvagara zviripo
+
+ Injini yekutsvaga
+
+ Batira
+
+ Dzimwa
+
+ URL Yapedziswa
+
+ Masaiti anoendwa zvakanyanya
+
+ Pema Saitsi awa isa woga
+
+ Ronga masaitsi
+
+ Ronga masaitsi
+
+ + Wedzera yaunodao URL
+
+ Wedzera yaunodao URL
+
+ Wedzera yaunodao URL
+
+ Isa ringi ku atokombuliti
+
+ Makuki uye Site Data
+
+ Zvaungada kuti zvibude
+
+ Bvisa aunoda aya URLs
+
+ Dzidza zvimwe
+
+ Wedzera uye gadzirisa aunoda anozvipedzisa URLs.
+
+ URL yaurikuda kuisa
+
+ Isa kana kunonyora kero
+
+ Muenzaniso: mozilla.org
+
+ Muenzaniso: muenzaniso.com
+
+ Itsva URL yakawedzerwa.
+
+ Bvisa
+
+ Bvisa
+
+ Tarisa kaviri URL yawaisa.
+
+ Mutauro
+ Magariro azvo musisitimu
+
+ Vanza
+ Vharira tunotsvaka kwauri twemaadhivhetsi
+ Zvimwe zvinoshambadzirwa zvinotengeswa nzvimbo yekushanyira nzvimbo, kunyange kana iwe usingadi kubaya adhivheti racho
+ Vharira tunotsvaka kwauri twemaadhivhetsi
+ Inoshandiswa kuunganidza, kuongorora uye kuenzanisa zvinhu zvakadai sokubaya uye kudzika
+ Vharira tunotsvaka twehupenyu
+ Yakabudiswa panzvimbo dzekutsvaga kutarisa kushanyira kwako uye kuratidza kushanda sezvikwata zvekugovera
+ Vharira vamwe vateereri vezvinhu
+ Ukabatidza mamwe mapeji anogona kushanda neutowo usiri iwo
+ Vhara makuki
+
+ Vharira 3-party tracker cookies chete
+ Vharira 3-party tracker cookies chete
+
+ Shandisa munwe kuvhura app
+
+ Hauchaoneke
+ Viga mapepa epakombiyuta paunoshandura mapurogiramu uye udzivise kutora masikirini shoti.
+
+ Chengetedzo
+
+ Maitiro
+ Vhara runyoro rwepaDande
+ Izvozvo zvinogona kukonzera kushaikwa kwemaayikoni kana mifananidzo
+
+ Vhara JavaScript
+ Mapeji anogona kuvhura nokukurumidza, asi anogonawo kutanga kuita zvisingatarisirwe
+
+ Ita %1$s bhurauza rechigarire
+
+ Mozilla
+ Tumira data maererano nemashandisirwo
+
+ Dzidza zvimwe
+
+ Mozilla inoedza kuunganidza chete zvatinoda kupa nekuvandudza %1$s kune munhu wose.
+
+ Chiziviso pamusoro pekuvanza
+
+ Nezvedu %1$s
+
+ Mainjini ekutsvaga akaiswa
+
+ Dzorera mainjini okutsvaga echigaririre
+
+ + Wedzera imwe injini yekutsvaga
+ Bvisa mainjini ekutsvaga
+ Bvisa
+
+ Wedzera injini yekutsvaga
+
+ Zita reinjini yekutsvaga
+ Manzwi ekushandisa pakutsvaga
+ Sevha
+
+ Muenzaniso: muenzaniso.com/search/?q=%s
+
+ Injini yekutsvaga itsva yaiswa.
+
+ Nyora zita reinjini yekutsvaga
+ Imwe injini yekutsvaga yakaiswa irikutoshandisa zita iroro.
+
+ Nyora mazwi ekutsvagisa
+
+ Cherechedza kuti tsananguro yekutsvaga yakafanana nemuenzaniso here
+
+ Dzima zvaiswa
+
+ Siyana nazvo
+
+ Bvisa zvawambotsvaga padande
+
+ Matebhu akavhurika: %1$s
+
+ Pinda zvakachengetedzwa
+
+ Zvavakuda kuita
+
+ Webhusaiti yavhurwa
+
+ Dzimwe sarudzo
+
+ Button rinoratidza zvimwe zvingaratidzwa
+
+ Enda kumberi
+
+ Vhura webhusaiti zvakare
+
+ Dzoka kumashure
+
+ Mira kuvhura webhusaiti
+
+ Dzoka ku app yekupedzisira
+
+ Nhamba yezvinotevera zvakavharwa
+
+ Vharira tunotsvaka kwauri
+
+ Kodzero dzako
+
+ Vhura link mune imwe app
+ Unogona kusiya %1$s kuti ugovhura zvinoenda kuna %2$s.
+ Tsvaga app inokwanisa kuvhura link
+ Tashaiwa app mufoni yako inokwanisa kuvhura link. Unokwanisa kusiya %1$s kuti ugotsaga %2$s kune app inozvikwanisa.
+ Wafunga kusiya kutsvaga pachivande?
+
+ %1$s zvapera
+
+ Vhura
+
+
+
+
+
+
+
+
+
+ Sevha harina kuoneka
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shandisa simba rako rechivande
+ Tora browsing pachedu kune imwe inotevera. Chengetedza zvikwereti nezvimwe zvinhu zvinogona kukutevera pazvikwata uye kuvhara pasi peji kutengesa nguva.
+
+ Kutsvaga kwako, inzira yako
+ Kutsvaka chimwe chinhu chakasiyana? Sarudza imwe shandiso yekutsvaga injini muZvirevo.
+
+ Wedzera zvidzitiro pamba peimba yako
+ Dzokera kunzvimbo dzako dzaunoda mu %1$s nokukurumidza. Ingosarudza \"Wedzera Pamba peji\" kubva ku %1$s menyu.
+
+ Ita kuchengeteka kwako chive chijairire
+ Ita %1$s sechitsvaga chako chekutsvaga uye uwane mararamiro ehupfumi hwepachivande paunotanga mapepa ewebhu kubva kune mamwe maitiro.
+
+ Horait, Ndazvibata!
+ Svetuka
+ Enda mberi
+
+ -
+
+ Wedzera
+
+ Kanzura
+
+ Sesheni yekutsvaga pachivande
+
+ Izaziso dzinokubvumira kuti usasase %1$s svondo yako nepombi. Iwe haufaniri kuzarura purogiramu yacho kana kuona kuti chii chiri kushanda mu browser yako.
+
+ Bvisa zvawamboona
+
+ Dounirodha Firefox
+
+ %1$s yakasununguka uye yakasununguka purogiramu software yakagadzirwa naMozilla nevamwe vanobatsira.
+
+
+
+
+ Zita rekushandisa
+ Pasiwedhi
+ Bvisa
+
+ Konekisheni yakachengeteka
+ Konekisheni haina kuchengeteka
+ Zvabvumikiswa na: %1$s
+
+ Saiti Sekiyuriti
+ URL yagara iriko
+
+ Tsvaga mupeji
+
+ Tsvaga papeji
+
+ %1$d/%2$d
+ %1$d ezvimwe %2$d
+
+ Tarisa zvinotevera zvabuda
+ Tarisa zvirikumashure zvabuda
+ Bvisa peji rakutsvaga
+
+
+
+ Kumbira peji repadhesikitopu
+
+ URL yakopwa
+
+ Maturuzi evanogadzira
+
+ Zviriadhivhanzidhi
+
+ Kugadzirisa usiripanzvimbo ne USB/Wi-Fi
+
+ Ayikoni yepikicha yechigunwe
+
+ Hatina kuziva chigunwe chako. Edza zvakare.
+
+ Chigunwe chamhanya zvakanyanya. Edza zvakare.
+
+ Kwete
+
+ Hongu
+
+ Mimwe injini dzinotsvaga haigone kuratidza mazano.
+
+ Siyana nazvo
+
+
+
+ Site yaita zvisingatarisirwi?\n Edza kubvisa Kuchengetedzwa Kwekutevera
+
+ Iva one-tap-access kune masayiti ako anoshandisa%1$s Menyu > Wedzera kusikirini rekutanga
+
+ Vhura mazano ose mu %1$s\n Ita %1$s sechekutsvagisa
+
+ Ma URLs echikwata zvemasayiti aunonyanya kushandisa\n Dhinda kwenguva ipi neipi URL mubhare rekero
+
+ Vhura lingi mune imwe tebhu\n Dhinda kwenguva refu chero chibatanidza pane peji
+
+ Dzima mazano papeji yekutangisa
+
+ Tebhu idzva yavhurwa
+
+ Chinja
+
+ Chinja ringi kune tebhu idzva pakarepo
+
+ Chengetedza nzvimbo dzingangove dzine ngozi uye dzinonyengedza
+ Vharira nzvimbo dzinonyengedza kunze uye dzekurwisa, nzvimbo dzisina kuwanikwa, uye dzisina kuwanikwa software.
+
+ Zvisarudzo
+ Wadzima Content Blocking nekuda kwemawebsite aya.
+ Bvisa
+ Bvisa mawebhusaiti ese
+
+ Tebhu rakirasha
+ Ruregerero. Taita dambudziko ne tebhu iri.
+ Sekuchengetedzwa kwepachivande, hatina kumbochengetedza uye hatigoni kudzorera tabu iyi.
+ Vhara Tebhu
+
+
+
+
+ Tumira ripoti yekuparara kuMozilla
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sq/strings.xml b/mobile/android/focus-android/app/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000000..e4f3961ba3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sq/strings.xml
@@ -0,0 +1,1127 @@
+
+
+
+
+
+
+
+
+ Anuloje
+
+ OK
+
+ Ruaje
+
+
+ Kërkoni ose jepni adresë
+
+ Shfletim automatikisht privat.\nShfletoni. Fshini Gjurmët. Përsëriteni.
+
+
+ Historiku juaj i shfletimit u fshi.
+ Historiku i shfletimit u spastrua
+
+
+ Historiku i shfletimit në skedë u fshi.
+
+
+ Kërko për %1$s
+
+
+ Ndajeni me të tjerët…
+
+
+ Raporto Problem Sajti
+
+
+ Hape në %1$s
+
+
+ Hapeni në…
+
+
+ Shtoje te skena e Kreut
+
+
+ Shtoje te Shkurtoret
+
+ Hiqe nga Shkurtoret
+
+
+ Rregullime
+ Rreth
+ Ndihmë
+ Të drejtat Tuaja
+
+
+ Gjurmues të bllokuar
+
+
+ Mbyllja e kësaj mund të ndreqë disa probleme sajti
+
+
+ Bllokim Lënde
+
+ Çaktivizojeni që të ndreqen disa sajte
+
+
+ Powered by %1$s
+
+
+ Ndajeni përmes
+
+ Të fshihet historik shfletimesh?
+ Që të fshihet në mënyrë të siguruar historiku juaj i shfletimit, prekeni, ose spastrojeni këtë njoftim.
+
+
+ Që të fshihet në mënyrë të siguruar historiku juaj i shfletimit, prekeni, ose fërkojeni këtë njoftim.
+
+ Fshini historik shfletimesh
+
+
+ Hape
+
+
+ Fshije dhe Hape
+
+
+ Fshije
+
+
+ Fshi historik shfletimesh
+
+
+
+ Fshije & hape
+
+
+ Fshini dhe hapni %1$s
+
+
+
+ Kërkojeni me Focus
+
+ Kërkojeni me Klar
+
+ Kërkojeni me Focus Beta
+
+ Kërkojeni me Focus Nightly
+
+
+ %1$s ju vë juve në kontroll.
+Përdoreni si një shfletues privat:
+
+ Kërkoni dhe shfletoni drejt e nga aplikacioni
+ Bllokoni gjurmues (ose përditësoni rregullimet për lejim gjurmuesish)
+ Fshini cookie-t dhe historik kërkimesh dhe shfletimesh
+
+
+%1$s prodhohet nga Mozilla. Misioni ynë është të nxisim një Internet të shëndetshëm, të hapët.
+Mësoni më tepër
]]>
+
+
+ Privatësi & Siguri
+
+
+ Gjurmim, “cookies”, zgjedhje mbi të dhënat
+
+
+ Caktoje parazgjedhje, vetëplotësim
+
+
+
+
+ Mbi %1$s, ndihmë
+
+
+ Mbrojtje e Thelluar Nga Gjurmimi
+
+
+ Lëndë Web
+
+
+ Këmbim Aplikacionesh
+
+
+ Të përgjithshme
+
+
+ Shfletues parazgjedhje, gjuhë
+
+
+ Grumbullim & Përdorim të Dhënash
+
+ Kërkim
+
+
+ Merrni këshillime kërkimi
+
+ %1$s do të dërgojë te motori juaj i kërkimeve atë çka shtypni te shtylla e adresave
+
+
+ Parazgjedhje
+
+
+ Motor kërkimesh
+
+
+ On
+
+
+ Off
+
+
+ Vetëplotësim URLje
+
+
+ Për sajtet Kryesues
+
+
+ Aktivizojeni, që %së vetëplotësojë te shtylla e adresave mbi 450 URL popullore.
+
+
+ Për Sajte Që Shtoni
+
+
+ Aktivizojeni, që %s të vetëplotësojë URL-të tuaja të parapëlqyera.
+
+
+ Administroni sajte
+
+
+ Administroni sajte
+
+
+ + Shtoni URL vetjake
+
+
+ Lista juaj e vetëplotësimeve:
+
+
+ Shtoni URL
+
+
+ Shtoni URL vetjake
+
+
+ Shtoni URL vetjake
+
+
+ Shtoni lidhje për vetëplotësim
+
+
+ “Cookies” dhe të Dhëna Sajtesh
+
+
+ Zgjedhje Mbi të Dhënat
+
+
+ Hiqni URLra Vetjake
+
+
+ Mësoni më tepër
+
+
+ Shtoni dhe administroni URLra vetjake vetëplotësimesh.
+
+
+ URL për shtim
+
+
+ Ngjitni ose jepni URL
+
+
+ Shembull: mozilla.org
+
+
+ Shembull: example.com
+
+
+ U shtua URL vetjake e re.
+
+
+ Hiqe
+
+
+ Hiqe
+
+
+ Rikontrollojeni URL-në që dhatë.
+
+ Gjuhë
+
+ Parazgjedhje sistemi
+
+ Privatësi
+ Blloko gjurmues reklamash
+ Disa reklama ndjekin vizita sajti, madje edhe kur s’klikoni mbi reklamat
+ Blloko gjurmues analizash
+ Përdorur për të grumbulluar, analizuar dhe matur veprimtari të tilla si prekje dhe rrëshqitje në faqe
+ Blloko gjurmues platformash shoqërore
+ Të trupëzuara në sajte për të gjurmuar vizitat tuaja dhe për të shfaqur funksione, të tillë si butona ndarjeje me të tjerët
+ Blloko të tjerë gjurmues lënde
+ Aktivizimi mund të bëjë që disa faqe të sillen ndryshe nga ç’pritet
+ Blloko cookie-t
+
+
+ Jo, faleminderit
+ Blloko vetëm cookie-t nga palë gjurmuese të treta
+ Blloko vetëm cookie-t nga palë të treta
+
+ Blloko “cookies” palësh të treta
+ Po, ju lutem
+
+
+ Përdor shenjë gishta për shkyçje aplikacioni
+
+
+ Shkyçeni duke përdorur shenjat tuaja të gishtave, nëse keni shtuar Shkurtore, ose kur një sajt është i hapur tashmë në %s.
+
+
+ Fshehtas
+
+ Fshihi faqet web kur kalohet nga një aplikacion në tjetrin dhe blloko bërje fotosh ekrani.
+
+ Siguri
+
+ Punim
+ Blloko shkronja Web
+
+ Mund të sjellë mungesë ikonash ose figurash
+
+ Blloko JavaScript
+
+ Faqet mund të ngarkohen më shpejt, por mundet gjithashtu të sillen në mënyrë të papritur
+
+
+ Bëje %1$s-un shfletuesin parazgjedhje
+
+ Mozilla
+ Dërgo të dhëna përdorimi
+
+
+ Mësoni më tepër
+
+
+ Mozilla rreket të grumbullojë vetëm sa na duhen për të ofruar dhe përmirësuar %1$s-un për këdo.
+
+
+ Shënim Privatësie
+
+
+ Hollësi licencimi
+
+
+ Biblioteka që përdorim
+
+
+ %s | Biblioteka OSS
+
+
+ Mbi %1$s-un
+
+
+ Motorë të instaluar
+
+
+ Zgjidhni motor kërkimesh
+
+
+ Rikthe motorë parazgjedhje kërkimi
+
+
+ + Shtoni motor tjetër kërkimesh
+ Hiqni motorë kërkimesh
+ Hiqe
+
+
+ Shtoni motor tjetër kërkimesh
+
+ Përzgjidhni motorin tuaj të parapëlqyer:
+
+
+ Shtoni motorë kërkimesh
+
+ Emër motori kërkimesh
+ Varg kërkimi për t’u përdorur
+ Ruaje
+
+
+ Shembull: example.com/search/?q=%s
+
+ Motori i ri i kërkimeve u shtua.
+
+ Jepni emër motori kërkimesh
+ Atë emër e përdor tashmë një motor i instaluar kërkimesh.
+
+ Jepni varg kërkimi
+
+ Kontrolloni që vargu i kërkimit të përputhet me formatin Shembull
+
+
+ Pastro ç’është dhënë
+
+
+ Hidhe tej
+
+
+ Fshi historik shfletimesh
+
+
+ Skeda të hapura: %1$s
+
+
+ Lidhje e sigurt
+
+
+ Po ngarkohet
+
+
+ Sajti u ngarkua
+
+
+ Më tepër mundësi
+
+
+ Buton për më tepër mundësi
+
+
+ Lëvizni përpara
+
+
+ Ringarko sajtin
+
+
+ Shko mbrapsht
+
+
+ Resht së ngarkuari sajtin
+
+
+ Kthehu te aplikacioni i mëparshëm
+
+
+ Numër gjurmuesish të bllokuar
+
+
+ Blloko gjurmues
+
+ Të drejtat Tuaja
+
+ Hape lidhjen me një aplikacion tjetër
+
+ Mund të lini %1$s-un ta hapë këtë lidhje me %2$s.
+
+ Gjeni një aplikacion që mund të hapë lidhjen
+
+ Asnjë nga aplikacionet në pajisjen tuaj s’është në gjendje të hapë këtë lidhje. Mund të lini %1$s-un të kërkojë në %2$s për një aplikacion që mundet.
+
+ Të dilet nga Shfletimi Privat?
+
+
+ %1$s përfundoi
+
+
+ Hape
+
+
+
+
+
+
+
+
+
+
+ U shtua te shkurtoret!
+
+ S’u gjet shërbyes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mbylle
+
+
+
+ Mirë se vini te %1$s
+
+
+ I shpejtë. Privat. Pa shpërqendrime.
+
+
+ Fillojani
+
+
+
+ %1$s s’është si shfletuesit e tjerë
+
+
+ E pastrojmë historikun tuaj, kur mbyllni aplikacionin, për më tepër privatësi.
+
+
+
+ Bëjeni %1$s-un parazgjedhjen tuaj, që të mbroni të dhënat tuaja për çdo lidhje që hapni.
+
+
+ Vëre si shfletuesin parazgjedhje
+
+
+ Anashkaloje
+
+
+
+ Fuqizoni privatësinë tuaj
+
+ Kalojeni shfletimin privat një shkallë më sipër. Bllokoni reklama dhe lëndë tjetër që mund t’ju gjurmojë nga sajte në sajte dhe të ulë kohën e ngarkimit të faqeve.
+
+
+ Kërkimi juaj, me mënyrën tuaj
+
+ Po kërkoni për diçka tjetër? Zgjidhni te Rregullimet një tjetër motor parazgjedhje kërkimesh.
+
+
+ Shtoni shkurtore te skena juaj e kreut
+
+ Rikthehuni shpejt te sajtet tuaj të parapëlqyer, me %1$s. Thjesht përzgjidhni “Shtoje te skena e Kreut”, që nga menuja e %1$s-ut.
+
+
+ Bëjeni zakon privatësinë
+
+ Vëreni %1$s-un si shfletuesin tuaj parazgjedhje dhe përfitoni të mirat e shfletimit privat, kur hapni faqe në web që nga aplikacione të tjera.
+
+ OK, e mora vesh!
+ Anashkaloje
+ Pasuesja
+
+
+ -
+
+
+ Shtoje
+
+
+ PO
+
+
+ Anuloje
+
+
+ JO
+
+
+ Shkurtorja do të hapet me Mbrojtjen e Thelluar Nga Gjurmimet të çaktivizuar
+
+
+ Sesion shfletimi privat
+
+
+ Njoftimet ju lejojnë të fshini me një prekje sesionin tuaj %1$s. S’ju duhet të hapni aplikacionin apo të shihni se ç’po xhiron në shfletuesin tuaj.
+
+
+ Fshi historik shfletimesh
+
+
+ Shkarkoni Firefox-in
+
+
+
+
+
+
+
+
+ Mozilla Public License dhe licencash të tjera burimi të hapët.]]>
+
+
+ këtu.]]>
+
+
+ licencash të tjera të ndryshme programesh të lira dhe me burim të hapët.]]>
+
+
+ GNU General Public License v3 dhe të passhme prej këtu .]]>
+
+
+ Emër përdoruesi
+ Fjalëkalim
+ Pastroje
+
+
+
+ Lidhje e Siguruar
+ Lidhje e Pasiguruar
+
+ Verifikuar nga: %1$s
+
+
+ Siguri Sajti
+ Ka tashmë një URL të tillë
+
+
+ Gjej në Faqe
+
+
+ Gje në faqe
+
+
+ %1$d/%2$d
+
+ %1$d nga %2$d
+
+
+ Gjej përfundimin pasues
+
+ Gjej përfundimin e mëparshëm
+
+ Hidhe tej gjetjen në faqe
+
+
+
+
+ Kërko sajtin për dekstop
+
+
+ Sajt për desktop
+
+
+ URL-ja u kopjua
+
+
+ Mjete zhvilluesi
+
+
+ Hapi lidhjet në aplikacione
+
+
+ Të mëtejshme
+
+
+ Leje sajti
+
+
+ Reduktim Banderolash Për Cookie-t
+
+
+ On
+
+
+ Off
+
+
+ Reduktim Banderolash Për Cookie-t
+
+
+ Shihni më pak banderola, duke hedhur poshtë automatikisht kërkesa për “cookie”, kur është e mundshme.
+
+ -->
+ Reduktim Banderolash Për Cookie-t
+
+
+ ON për këtë sajt
+
+
+ Sajt aktualisht i pambuluar
+
+
+ OFF për këtë sajt
+
+
+ Reduktim Banderolash Për Cookie-t
+
+
+ OFF për këtë sajt
+
+
+ ON për këtë sajt
+
+
+ Të aktivizohet Reduktim Banderolash Cookie-sh për %1$s?
+
+
+ Të çaktivizohet Reduktim Banderolash Cookie-sh për %1$s?
+
+
+ %1$s do të spastrojë cookie-t për këtë sajt dhe do të rifreskojë faqen. Spastrimi i krejt cookie-ve mund të sjellë nxjerrjen tuaj nga llogaria, ose zbrazje shportash blerjesh.
+
+
+ %1$s mund të provojë vetvetiu të hedhë poshtë kërkesa depozitimi cookie-sh.
+
+
+ Aktualisht ky sajt nuk mbulohet nga Reduktim Banderolash Cookie-sh. Do të donit që ekipi ynë ta shqyrtojë këtë sajt dhe të shtojcë mbulim për të në të ardhmen?
+
+
+ Anuloje
+
+
+ Kërkoni asistencë
+
+
+ Kërkesa te sajti i asistencës u parashtrua.
+
+
+ Kërkesa te sajti i asistencës u parashtrua.
+
+
+
+ %1$s rreket të hedhë poshtë kërkesa depozitimi cookie-sh, për hedhur tej banderola bezdisëse cookie-sh.\n\nAdministroni parapëlqime banderolash cookie-sh, te %2$s.
+
+ rregullime
+
+
+ Vetëluaje
+
+
+ Që ta lejoni:
+
+
+ 1. Shkoni te Android Settings
+
+
+ Permissions]]>
+
+
+ Kaloni te Rregullimet
+
+
+ %1$s si ON]]>
+
+
+ Kamerë
+
+
+ Mikrofon
+
+
+ Vendndodhje
+
+
+ Njoftim
+
+
+ Lëndë nën DRM
+
+
+ Kërko të lejohet
+
+
+ E bllokuar
+
+
+ E lejuar
+
+
+ E bllokuar nga Android
+
+
+ Lejo audio dhe video
+
+
+ Blloko vetëm audio
+
+
+ E këshilluar
+
+
+ Blloko audio dhe video
+
+
+ Studime
+
+
+ Firefox-i mund të instalojë dhe zhvillojë studime herë pas here.
+
+
+ Mësoni më tepër
+
+
+ Aplikacioni do të mbyllet, që të vihen në fuqi ndryshimet
+
+
+ Hiqe
+
+
+ Aktiv
+
+
+ I plotësuar
+
+
+ Diagnostikim i largët përmes USB/Wi-Fi
+
+
+ Shkyçe
+
+
+ Ripohoni Përdorimin e Shenjave Tuaja të Gishtave
+
+
+ Mund të përdorni shenjat tuaja të gishtave për të vazhduar sesionin e tanishëm të aplikacionit.
+
+
+ Hape Lidhjen në Sesion të Ri
+
+
+ Ikonë shenjash gishtash
+
+
+ Shenja gishta të papranuar. Riprovoni.
+
+
+ Gishti lëvizni shumë shpejt. Riprovoni.
+
+
+ Të shfaqen sugjerime kërkimi?
+
+
+ Që të merrni sugjerime, %1$s ka nevojë të dërgojë te motori juaj i kërkimeve atë çka shtypni te shtylla e adresave.
+
+
+ Jo
+
+
+ Po
+
+
+ Disa motorë kërkimesh s’shfaqin dot këshillimet.
+
+
+ Mos e merr parasysh
+
+
+
+
+ Sajt që sillet çuditshëm?\n
+ Provoni çaktivizimin e Mbrojtjes Nga Gjurmimi
+
+
+ Shtoje te skena Kreu]]>
+
+
+ Hape çdo lidhje në %1$s\n
+ Caktojeni %1$s si shfletuesin parazgjedhje
+
+
+
+ Vetëplotësim URL-sh për sajtet që përdorni më shpesh\n
+ Shtypni paksa çfarëdo URL-je te shtylla e adresave
+
+
+
+ Hapni një lidhje në një skedë të re\n
+ Shtypni paksa çfarëdo lidhje në një faqe
+
+
+
+ Mbylli ndihmëzat te skena e nisjes
+
+
+ U hap skedë e re
+
+
+ Kalo në të
+
+
+ Po hyhet në mënyrën “Sa krejt ekrani”
+
+
+ Kalo menjëherë te lidhja në skedën e re
+
+
+ Bllokoni sajte potencialisht të rrezikshëm dhe të rrejshëm
+
+ Bllokoni sajte të raportuar si të rrejshëm dhe sulmesh, sajte malware-i dhe sajte software-i të padëshiruar.
+
+
+ Mënyra Vetëm-HTTPS
+
+
+ Përpiqet automatikisht të lidhet me sajtet duke përdorur protokollin HTTPS të fshehtëzimit, për më tepër siguri.
+
+
+ Përjashtime
+
+ E keni çaktivizuar Bllokimin e Lëndës për këto sajte.
+
+ Hiqe
+
+ Hiqi krejt sajtet
+
+
+ Blloko Cookie-t
+
+
+ Doni të bllokohen cookie-t?
+
+
+ Skeda u Vithis
+
+ Na ndjeni. Po kemi një problem me këtë skedë.
+
+ Si një shfletues privat, nuk e ruajmë kurrë dhe nuk mund të rikthejmë këtë skedë.
+
+ Mbylle Skedën
+
+
+
+
+
+ Dërgoni raport vithisje te Mozilla
+
+
+
+
+ Gjurmues të bllokuar që prej %s
+
+ Lëndë
+
+ Reklamim
+
+ Shoqërore
+
+ Analiza
+
+ Mbrojtje e Thelluar Nga Gjurmimi
+
+ Për këtë sajt, mbrojtjet janë OFF
+
+ Për këtë sajt, mbrojtjet janë ON
+
+ Lidhja është e siguruar
+
+ Lidhja s’është e siguruar
+
+
+ Gjurmues dhe Programthe për t’u Bllokuar
+
+
+ Kthehu mbrapsht
+
+
+
+ Hiqe
+
+
+ Riemërtojeni
+
+ Riemërtojeni
+
+
+ Emër shkurtoreje
+
+
+ Figurat e ruajtura dhe ato të ndara me të tjerët <b>s’do të fshihen</b>, kur të fshini historikun e %1$s-ut
+
+
+
+ Temë
+
+ E çelët
+
+ E errët
+
+ Caktuar nga Kursyesi i Baterisë
+
+ Ndiq temën e pajisjes
+
+
+
+ Ky sajt nuk mbulon HTTPS
+
+
+ Mësoni më tepër
+ Ndryshojeni këtë rregullim te Rregullime > Privatësi & Siguri > Siguri.]]>
+
+
+ Lidhje jo e sigurt
+
+
+
+ Nëse në të kaluarën jeni lidhur me sukses në këtë shërbyes, gabimi mund të jetë i përkohshëm.
+ ]]>
+
+
+ Dikush mund të jetë duke u rrekur të hiqet si sajti dhe vazhdimi mund të jetë me rrezik.
+
+ %1$s nuk i zë besë %2$s m ngaqë lëshuesi i dëshmisë së tij është i panjohur, dëshmia është e vetënënshkruar, ose shërbyesi s’po dërgon dëshminë e saktë ndërmjetëse.
+ ]]>
+
+
+
+ Mbylle skedën
+
+
+
+ I mbërthyem! E ndalëm spiunimin tuaj nga ky sajt. Prekni kurdo mburojën, që të shihni se ç’jemi duke bllokuar.
+
+
+ Mbylleni flluskën
+
+
+
+ Jeni i mbrojtur!
+
+ Këto rregullime parazgjedhje ofrojnë mbrojtje të fuqishme. Por s’është e lehtë të përimtohen gjërat, për të plotësuar nevojat tuaja specifike.
+
+ Hidhe tej
+
+
+ Prekni këtu, që të shpihet e gjitha në hedhurina — historik, “cookies”, gjithçka — dhe fillojani nga e para me një skedë të re.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Mbylle
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget kërkimesh
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Historiku i shfletimit u spastrua! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Filloni sesionin tuaj të shfletimit privat dhe ne do të bllokojmë dora-dorës gjurmues dhe gjëra të tjera të këqija.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Do t’ju lëmë në shfletimin tuaj privat, por siguroni një kohë më të mirë nisjeje herës tjetër, me widget-in %1$s në skenën tuaj Kreu.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Shtojeni widget-in te skena e kreut
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget-i u shtua te skena e kreut
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sr/strings.xml b/mobile/android/focus-android/app/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000000..04d39f5d87
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sr/strings.xml
@@ -0,0 +1,1114 @@
+
+
+
+
+
+
+
+
+ Откажи
+
+ У реду
+
+ Сачувај
+
+
+ Претражите или унесите адресу
+
+ Аутоматско приватно сурфовање.\nСурфуј. Обриши. Понови.
+
+
+ Ваша историја прегледања је обрисана.
+ Историја прегледања је обрисана
+
+
+ Историја прегледања језичка је избрисана.
+
+
+ Претражи %1$s
+
+
+ Подели…
+
+
+ Пријави проблем са сајтом
+
+
+ Отвори у %1$s
+
+
+ Отвори у…
+
+
+ Додај на почетни екран
+
+
+ Додај у пречице
+
+ Уклони из пречица
+
+ Поставке
+ О Focus-у за Android
+ Помоћ
+ Ваша права
+
+
+ Блокирани пратиоци
+
+
+ Онемогућавање ове опције може решити неке проблеме са сајтом
+
+
+ Блокирање садржаја
+
+ Онемогућите да бисте поправили неке сајтове
+
+
+ Покреће га %1$s
+
+
+ Подели преко
+
+
+ Обришите историју прегледања
+
+
+ Отвори
+
+
+ Обриши и отвори
+
+
+ Обриши
+
+
+ Обриши историју прегледања
+
+
+
+ Обриши и отвори
+
+
+ Обриши и отвори %1$s
+
+
+
+ Тражи у Focus-у
+
+ Тражи у Klar-у
+
+ Тражи у Focus Beta-и
+
+ Тражи у Focus Nightly-ју
+
+
+ %1$s вам даје контролу.
+Користите га као приватни прегледач:
+
+ Претражујте и сурфујте директно у апликацији
+ Блокирајте елементе за праћење (или их омогућите у подешавањима)
+ Избришите све колачиће као и историју прегледања и претраге
+
+
+%1$s је створила Mozilla. Наша мисија је неговање здравог и отвореног интернета.
+Сазнајте више
]]>
+
+
+ Приватност и безбедност
+
+
+ Праћење, колачићи, избори података
+
+
+ Постави подразумевано, аутоматски заврши
+
+
+
+
+ О %1$s-у, помоћ
+
+
+ Побољшана заштита од праћења
+
+
+ Веб садржај
+
+
+ Мењање апликација
+
+
+ Општа
+
+
+ Подразумевани прегледач, језици
+
+
+ Сакупљање и коришћење података
+
+ Претраживач
+
+
+ Добијајте предлоге претраге
+
+ %1$s ће послати оно што куцате у адресну траку вашег претраживача
+
+
+ Подразумевано
+
+
+ Претраживач
+
+
+ Укључено
+
+
+ Искључено
+
+
+ URL aутоматско завршавање
+
+
+ За врхунске сајтове
+
+
+ Омогућите да %s аутоматски завршава преко 450 популарних URL адреса у адресној траци.
+
+
+ За сајтове које додате
+
+
+ Омогућите да %s аутоматски довршава ваше омиљене URL адресе.
+
+
+ Уредите сајтове
+
+
+ Уредите сајтове
+
+
+ + Додај URL
+
+
+ Ваша листа аутоматског довршавања:
+
+
+ Додајте URL
+
+
+ Додај URL
+
+
+ Додај URL
+
+
+ Додајте везу за аутоматско довршавање
+
+
+ Колачићи и подаци сајта
+
+
+ Избори података
+
+
+ Уклони URL адресе
+
+
+ Сазнајте више
+
+
+ Додавање и управљање аутоматски завршеним URL адресама.
+
+
+ URL за додавање
+
+
+ Налепите или унесите URL
+
+
+ Пример: mozilla.org
+
+
+ Пример: example.com
+
+
+ Нови URL додат.
+
+
+ Уклони
+
+
+ Уклони
+
+
+ Добро проверите URL који сте унели.
+
+ Језик
+
+ Системски подразумеван
+
+ Приватност
+ Блокирај оглашиваче
+ Неке рекламе прате посете сајтова чак и када не кликнете на њих
+ Блокирај аналитичке пратиоце
+ Користи се за сакупљање, анализирање и мерење активности као што су кликтање и скроловање
+ Блокирај друштвене пратиоце
+ Уграђене у сајтове како би пратиле ваше посете и приказивале дугмиће за дељење
+ Блокирај пратиоце осталог садржаја
+ Омогућавање може утицати лоше на неке странице
+ Блокирај колачиће
+
+
+ Не, хвала
+ Блокирајте само колачиће треће стране
+ Само колачиће од трећих лица
+
+ Блокирајте колачиће трећих страна
+ Да, молићу
+
+
+ Користите отисак прста да откључате апликацију
+
+
+ Ако %s има отворене веб странице или додане пречице, користите отисак прста за откључавање.
+
+
+ Потајни режим
+
+ Сакриј веб странице приликом мењања између апликација и блокирај бележење снимка екрана.
+
+ Безбедност
+
+ Перформансе
+ Блокирај веб фонтове
+
+ Иконице и слике се можда неће приказивати
+
+ Блокирај JavaScript
+
+ Странице се могу брже учитавати, али се могу и лоше понашати
+
+
+ Постави %1$s као подразумевани прегледач
+
+ Mozilla
+ Шаљи податке о употреби
+
+
+ Сазнајте више
+
+
+ Mozilla тежи ка прикупљању само оних података који су потребни да бисмо пружали и побољшали %1$s за све.
+
+
+ Обавештење о приватности
+
+
+ Подаци о лиценцирању
+
+
+ Библиотеке које користимо
+
+
+ %s | библиотеке отвореног кода
+
+
+ О %1$s
+
+
+ Инсталирани претраживачи
+
+
+ Изаберите претраживач
+
+
+ Врати на подразумеване
+
+
+ + Додај претраживач
+ Уклони претраживач
+ Уклони
+
+
+ Додајте други претраживач
+
+ Изаберите омиљени претраживач:
+
+
+ Додај претраживач
+
+ Име претраживача
+ Претражи текст за коришћење
+ Сачувај
+
+
+ Пример: example.com/search/?q=%s
+
+ Нови претраживач додат.
+
+ Унесите име претраживача
+ Инсталирани претраживач већ користи то име.
+
+ Унесите текст за претрагу
+
+ Проверите да ли се текст за претрагу поклапа са форматом примера
+
+
+ Празно поље
+
+
+ Одбаци
+
+
+ Обриши историју прегледања
+
+
+ Отворених језичака: %1$s
+
+
+ Безбедна веза
+
+
+ Учитавање
+
+
+ Веб сајт учитан
+
+
+ Више опција
+
+
+ Дугме за више опција
+
+
+ Иди напред
+
+
+ Обнови веб сајт
+
+
+ Иди назад
+
+
+ Престани са учитавањем веб сајта
+
+
+ Повратак на претходну
+
+
+ Број блокираних пратиоца
+
+
+ Блокирај пратиоце
+
+ Ваша права
+
+ Отворите везу у другој апликацији
+
+ Можете угасити %1$s да отворите ову везу у %2$s.
+
+ Пронађите апликацију која може отворити ову везу
+
+ Ниједна апликација на вашем уређају не може отворити ову везу. Можете угасити %1$s и претражити %2$s да бисте нашли неку.
+
+ Изаћи из приватног режима?
+
+
+ %1$s завршено
+
+
+ Отвори
+
+
+
+
+
+
+
+
+
+
+ Додато у пречице!
+
+ Сервер није пронађен
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Затвори
+
+
+
+ Добродошли у %1$s
+
+
+ Брзо. Приватно. Без ометања.
+
+
+ Започните
+
+
+
+ %1$s није као други прегледачи
+
+
+
+ У сврху додатне приватности, бришемо вашу историју када затворите апликацију.
+
+
+
+ Постављањем %1$s-а подразумеваним, штитите податке са сваком везом коју отворите.
+
+
+ Постави као подразумевани прегледач
+
+
+ Прескочи
+
+
+
+ Ојачајте вашу приватност
+
+ Побољшајте приватност током сурфовања. Блокирајте рекламе и друге саржаје који вас прати путем сајтова и убрзајте учитавање страница.
+
+
+ Ваше претраге, ваш начин
+
+ Тражите нешто другачије? Изаберите други подразумевани претраживач у поставкама.
+
+
+ Додајте пречице на вашем почетном екрану
+
+ Вратите се на ваше омиљене сајтове у %1$s-у лако. Само изаберите \"Додај на екран телефона\" из %1$s менија.
+
+
+ Учините приватност навиком
+
+ Поставите %1$s као ваш подразумевани прегледач и побољшајте приватност током сурфовања када отворите веб странице из других апликација.
+
+ Разумем!
+ Прескочи
+ Следеће
+
+
+ -
+
+
+ Додај
+
+
+ ДА
+
+
+ Откажи
+
+
+ НЕ
+
+
+ Отвориће се пречица са искљученом побољшаном заштитом од праћења
+
+
+ Сесија за приватно прегледање
+
+
+ Обавештења вам омогућавају да обришете вашу %1$s сесију једним притиском. Не морате да отворите апликацију или да проверите шта се извршава у вашем прегледачу.
+
+
+ Обриши историју прегледања
+
+
+ Преузмите Firefox
+
+
+
+
+
+
+
+
+ Mozilla јавне лиценце и других лиценци отвореног кода.]]>
+
+
+ овде.]]>
+
+
+ лиценцама отвореног кода.]]>
+
+
+ GNU General Public License v3 и доступним овде .]]>
+
+
+ Корисничко име
+ Лозинка
+ Обриши
+
+
+
+ Безбедна веза
+ Несигурна веза
+
+ Проверио: %1$s
+
+
+ Безбедност сајта
+ URL већ постоји
+
+
+ Пронађи на страници
+
+
+ Пронађи на страници
+
+
+ %1$d/%2$d
+
+ %1$d од %2$d
+
+
+ Нађи следећи резултат
+
+ Нађи претходни резултат
+
+ Склони претрагу странице
+
+
+
+
+ Захтевај десктоп сајт
+
+
+ Десктоп приказ
+
+
+ URL копиран
+
+
+ Алати за програмере
+
+
+ Отвори везе у апликацијама
+
+
+ Напредно
+
+
+ Дозволе на страници
+
+
+ Смањење банера колачића
+
+
+ Укључено
+
+
+ Искључено
+
+
+ Смањење банера колачића
+
+
+ За мање банера аутоматским одбијањем захтева за колачиће, када год је то могуће.
+
+ -->
+ Смањење банера колачића
+
+
+ УКЉУЧЕНО за овај сајт
+
+
+ Сајт тренутно није подржан
+
+
+ ИСКЉУЧЕНО за овај сајт
+
+
+ Смањење банера колачића
+
+
+ ИСКЉУЧЕНО за овај сајт
+
+
+ УКЉУЧЕНО за овај сајт
+
+
+ Укључити смањење банера колачића за %1$s?
+
+
+ Икључити смањење банера колачича за %1$s?
+
+
+ %1$s ће обрисати колачиће и освежити страницу. Брисање колачића може да вас одјави са сајта или да испразни вашу корпу за куповину.
+
+
+ %1$s може аутоматски да одбије захтеве за колачиће.
+
+
+ Смањење банера колачића тренутно није подржано за овај сајт. Да ли бисте желели да наш тим прегледа овај сајт и подржи га у будућности?
+
+
+ Откажи
+
+
+ Затражите подршку
+
+
+ Захтев за подршку сајта је поднет.
+
+
+ Захтев за подршку сајта је поднет.
+
+
+
+ %1$s покушава да одбије захтеве за колачиће да би уклонио досадне банере колачића.\n\nИдите у %2$s да управљате банерима колачића.
+
+
+ подешавања
+
+
+ Самостално покретање
+
+
+ Да бисте дозволили:
+
+
+ 1. Идите на Android подешавања
+
+
+ Дозволе]]>
+
+
+ Иди у подешавања
+
+
+ %1$s на УКЉУЧЕНО]]>
+
+
+ Камера
+
+
+ Микрофон
+
+
+ Локација
+
+
+ Обавештење
+
+
+ Садржај контолисан DRM-ом
+
+
+ Питај за дозволу
+
+
+ Блокирано
+
+
+ Дозвољено
+
+
+ Блокирао Android
+
+
+ Дозволи звук и видео
+
+
+ Блокирај само звук
+
+
+ Препоручено
+
+
+ Блокирај звук и видео
+
+
+ Студије
+
+
+ Firefox може с времена на време инсталирати и спровести студије.
+
+
+ Сазнајте више
+
+
+ Апликација ће се затворити како би применила промене
+
+
+ Уклони
+
+
+ Активно
+
+
+ Завршено
+
+
+ Удаљено отклањање грешака уз USB/Wi-Fi
+
+
+ Откључај
+
+
+ Потврдите отиском прста
+
+
+ Можете да користите отисак прста да наставите тренутну сесију апликације.
+
+
+ Отвори везу у новој сесији
+
+
+ Иконица отиска прста
+
+
+ Отисак прста није препознат. Покушајте поново.
+
+
+ Превише брзо сте превукли прстом. Покушајте поново.
+
+
+ Приказати предлоге за претрагу?
+
+
+ Да би вам приказао предлоге за претрагу, %1$s мора да пошаље ваше уносе адресној траци претраживачу.
+
+
+ Не
+
+
+ Да
+
+
+ Неки претраживачи не могу приказивати предлоге.
+
+
+ Склони
+
+
+
+
+ Сајт се понаша чудно?\n Покушајте искључити заштиту од праћења
+
+
+ Додај на почетни екран]]>
+
+
+ Отвори сваку везу у %1$s-у\n Постави %1$s као подразумевани прегледач
+
+
+ Аутоматско завршавање URLs за сајтове које користите највише\n Дуго држите URL у адресној траци
+
+
+ Отвори везу у новом језичку\n Дуго држите на било којој вези на страници
+
+
+ Искључите савете на почетном екрану
+
+
+ Нови језичак је отворен
+
+
+ Пребаци
+
+
+ Улазим у режим целог екрана
+
+
+ Одмах пређите на везу у новом језичку
+
+
+ Блокирајте потенцијално опасне и обмањујуће сајтове
+
+
+ Блокирајте злонамерне и обмањујуће сајтове, пријављене злонамерне програме и нежељене сајтове.
+
+
+ Режим „Само HTTPS”
+
+ Аутоматски ће покушати да се повеже са страницама користећи HTTPS протокол за шифровање ради додатне безбедности.
+
+
+ Изузеци
+
+ Зауставили сте блокирање садржаја за ове сајтове.
+
+ Уклони
+
+
+ Уклоните све сајтове
+
+
+ Блокирајте колачиће
+
+
+ Желите ли да блокирате колачиће?
+
+
+ Језичак се срушио
+
+
+ Жао нам је. Имамо проблем са овим језичком.
+
+ Као приватни прегледач, никада не чувамо и не можемо да вратимо овај језичак.
+
+ Затворите језичак
+
+
+ Пошаљите Mozilla-и извештај о паду
+
+
+
+
+ Трагача блокирано од %s
+
+ Садржај
+
+ Маркетинг
+
+ Друштвене мреже
+
+ Аналитика
+
+ Побољшана заштита од праћења
+
+ Заштита је ИСКЉУЧЕНА за овај сајт
+
+ Заштита је УКЉУЧЕНА за овај сајт
+
+ Веза је безбедна
+
+ Веза није безбедна
+
+ Трагачи и скрипте за блокирање
+
+
+ Иди назад
+
+
+
+ Уклони
+
+
+ Преименуј
+
+ Преименуј
+
+ Назив пречице
+
+
+ Дељене или сачуване слике <b>неће</b> бити избрисане када обришете %1$s историју
+
+
+
+ Тема
+
+ Светла
+
+ Тамна
+
+
+ Према подешавањима за штедњу батерије
+
+ Прати тему уређаја
+
+
+ Овај сајт не подржава HTTPS
+
+
+ Сазнајте више
+ Промените ову поставку у Подешавања > Приватност и безбедност > Безбедност.]]>
+
+
+ Веза није безбедна
+
+
+
+ Ако сте се раније успешно повезали са овим сервером, могуће је да проблем привремен.
+ ]]>
+
+
+ Неко можда покушава да имитира сајт и настављање на исти је тада ризично.
+
+ %1$s нема поверења у %2$s зато што му је издавач сертификата непознат, сертификат је самопотписан или сервер не шаље исправне посредничке сертификате.
+ ]]>
+
+
+
+ Затвори језичак
+
+
+
+ Имамо га! Спречили смо ову веб страницу да вас шпијунира. Додирните штит било када да видите шта блокирамо.
+
+
+ Затвори искачући прозор
+
+
+
+ Заштићени сте!
+
+ Ова подразумевана подешавања већ пружају снажну заштиту, али их је лако прилагодити у складу са вашим потребама.
+
+ Одбаци
+
+
+ Додирните овде да обришете све — историју, колачиће, итд. — и почнете на новом језичку.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Затвори
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Виџет претраге
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Историја прегледања обрисана! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Започните са приватним прегледањем, а ми ћемо да блокирамо елементе за праћење и друге лоше ствари.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Остављамо вас вашем приватном прегледању, али следећи пут почните брже преко %1$s виџета на почетном екрану.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Додај виџет на почетни екран
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Виџет додан на почетни екран
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-su/strings.xml b/mobile/android/focus-android/app/src/main/res/values-su/strings.xml
new file mode 100644
index 0000000000..dad5f7fdd4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-su/strings.xml
@@ -0,0 +1,1115 @@
+
+
+
+
+
+
+
+
+ Bolay
+
+ HEUG
+
+ Teundeun
+
+
+ Paluruh atawa asupkeun alamat
+
+ Langlangan nyamuni otomatis.\nLanglang. Pupus. Deui.
+
+
+ Jujutan langlangan nyamuni anjeun geus dipupus.
+ Data langlangan diberesihan
+
+
+ Jujutan langlangan tab geus dipupus.
+
+
+ Paluruh %1$s
+
+
+ Bagikeun…
+
+
+ Laporkeun Masalah Loka
+
+
+ Buka di %1$s
+
+
+ Buka di …
+
+
+ Tambahkeun ka layar Tepas
+
+
+ Tambah ka Takulan
+
+ Piceun tina Takulan
+
+
+ Setélan
+ Ngeunaan
+ Pitulung
+ Hak Anjeun
+
+
+ Palacak dipeungpeuk
+
+
+ Mareuman ieu bisa menerkeun sababaraha masalah loka
+
+
+ Meungpeuk Kontén
+
+ Pareuman pikeun menerkeun sababaraha loka
+
+
+ Dijalankeun ku %1$s
+
+
+ Bagikeun kana
+
+ Pupus jujutan langlangan?
+ Toél atawa beresihan iber ieu pikeun mupus jujutan langlangan anjeun kalayan aman.
+
+
+ Toél atawa usap ieu iber pikeun mupus jujutan langlangan anjeun kalayan aman.
+
+ Pupus jujutan langlangan
+
+
+ Buka
+
+
+ Pupus jeung Buka
+
+
+ Pupus
+
+
+ Pupus jujutan langlangan
+
+
+
+ Pupus & buka
+
+
+ Pupus jeung buka %1$s
+
+
+
+ Paluruh di Focus
+
+ Paluruh di Klar
+
+ Paluruh di Focus Beta
+
+ Paluruh di Focus Nightly
+
+
+ %1$s méré anjeun kadali.
+Paké pikeun panyungsi nyamuni:
+
+ Maluruh jeung nyungsi langsung dina aplikasina
+ Meungpeuk palacak (atawa apdét setélan pikeun nyatujuan palacak)
+ Mupus pikeun ngaleungitkeun réréméh pon kitu ogé jujutan pamaluruhan jeung panyungsian
+
+
+%1$s dijieun ku Mozilla. Misi kami pikeun ngamumulé Internét nembrak anu séhat.
+Lenyepan
]]>
+
+
+ Salindungan & Kaamanan
+
+
+ Palacakan, réréméh, pilihan data
+
+
+ Setél baku, otokumplit
+
+
+
+
+ Ngeunaan %1$s, pitulung
+
+
+ Protéksi Palacakan Tingkat Lanjut
+
+
+ Eusi Raramat
+
+
+ Pindah Apps
+
+
+ Umum
+
+
+ Panyungsi, basa baku
+
+
+ Pangumpulan & Pamakéan Data
+
+ Paluruh
+
+
+ Cokot bongbolongan maluruh
+
+ %1$s bakal ngirim naon anu diketikkeun dina bilah alamat kana mesin pamaluruh anjeun
+
+
+ Baku
+
+
+ Mesin pamaluruh
+
+
+ Hurung
+
+
+ Pareum
+
+
+ URL Otokumplit
+
+
+ Pikeun loka Petingan
+
+
+ Hurungkeun sangkan %s otomatis ngalengkepan leuwih ti 450 URLs populér dina palang alamat.
+
+
+ Pikeun Loka anu Anjeun Nambahkeun
+
+
+ Hurungkeun sangkan %s otomatis ngalengkepan URLs karesep anjeun.
+
+
+ Kokolakeun loka
+
+
+ Kokolakeun loka
+
+
+ + Tambah URL sakahayang
+
+
+ Daptar otokumplit anjeun:
+
+
+ Tambah URL
+
+
+ Tambah URL sakahayang
+
+
+ Tambah URL sakahayang
+
+
+ Tambahkeun tutumbu kana autokumplit
+
+
+ Data Réréméh jeung Loka
+
+
+ Pilihan Data
+
+
+ Piceun URL sakahayang
+
+
+ Lenyepan
+
+
+ Tambah jeung atur URL otokumplit sakahayang.
+
+
+ URL tambahkeuneun
+
+
+ Terapkeun atawa asupkeun URL
+
+
+ Conto: mozilla.org
+
+
+ Conto: example.com
+
+
+ URL sakahayang anyar geus ditambahkeun.
+
+
+ Piceun
+
+
+ Piceun
+
+
+ Pariksa deui URL anu diasupkeun.
+
+ Basa
+
+ Baku sistem
+
+ Privasi
+ Peungpeuk palacak iklan
+ Sababaraha iklan ngalacak anjangan loka, najan anjeun teu ngaklik iklanna
+ Peungpeuk palacak analitik
+ Dipaké pikeun ngumpulkeun, analisis, jeung ngukur ketak sarupaning tap jeung gorolong
+ Peungpeuk palacak sosial
+ Naplok dina loka pikeun ngalacak anjangan anjeun sarta pikeun némbongkeun fungsionalitas sarupaning tumbul babagi
+ Peungpeuk palacak kontén séjén
+ Ngahurungkeun bisa ngabalukarkeun sababaraha kaca jadi ngaco
+ Peungpeuk réréméh
+
+
+ Moal, nuhun
+ Peungpeuk réréméh palacak pihak ka-3 hungkul
+ Peungpeuk réréméh pihak katilu hungkul
+ Peungpeuk réréméh meuntas-loka
+ Mangga
+
+
+ Paké sidik ramo pikeun muka konci app
+
+
+ Buka konci maké sidik ramo upama anjeun geus nambahkeun Markah atawa upama hiji raramatloka geus muka di %s.
+
+
+ Nyiliwuri
+
+ Sumputkeun kaca raramat nalika mindahkeun app sarta meungpeuk nyokot téwakan layar.
+
+ Kaamanan
+
+ Kinerja
+ Peungpeuk aksara Raramat
+
+ Matak leungit ikon atawa gambar
+
+ Peungpeuk JavaScript
+
+ Kaca meureun ngamuat leuwih gancang, ngan bisa waé teu katerka
+
+
+ Jadikeun %1$s panyungsi baku
+
+ Mozilla
+ Kirim data pamakéan
+
+
+ Lenyepan
+
+
+ Mozilla bakal kukumpul data nu diperlukeun hungkul jang nyadiakeun jeung ngoméan %1$s pikeun balaréa.
+
+
+ Iber Privasi
+
+
+ Émbaran lisénsi
+
+
+ Pabukon anu dipaké
+
+
+ %s | Pabukon OSS
+
+
+ Ngeunaan %1$s
+
+
+ Mesin pamaluruh nu dipasang
+
+
+ Pilih mesin pamaluruh
+
+
+ Pasang deui mesin pamaluruh baku
+
+
+ + Tambah mesin pamaluruh séjén
+ Piceun mesin pamaluruh
+ Piceun
+
+ Tambah mesin pamaluruh séjén
+
+
+ Pilih mesin karesep anjeun:
+
+
+ Tambah mesin pamaluruh
+
+ Ngaran mesin pamaluruh
+ Tambah string pakéeun
+ Teundeun
+
+
+ Conto: conto.com/search/?q=%s
+
+ Mesin pamaluruh anyar ditambahkeun.
+
+ Asupkeun ngaran mesin pamaluruh
+ Mesin pamaluruh nu kapasang geus aya kalawan ngaran nu sarupa.
+
+ Asupkeun string pamaluruhan
+
+ Pariksa yén string pamaluruhan akur jeung format Conto
+
+
+ Bersihan asupan
+
+
+ Tutup
+
+
+ Beresihan jujutan langlangan
+
+
+ Tab muka: %1$s
+
+
+ Sambungan aman
+
+
+ Ngamuat
+
+
+ Raramatloka wéb geus dimuat
+
+
+ Pilihan séjén
+
+
+ Tombol pilihan lianna
+
+
+ Maju
+
+
+ Muat ulang raramatloka
+
+
+ Mundur
+
+
+ Eureunan ngamuat raramatloka
+
+
+ Balik deui ka aplikasi saméméhna
+
+
+ Jumlah palacak nu dipeungpeuk
+
+
+ Peungpeuk palacak
+
+ Hak Anjeun
+
+ Buka tutumbu di aplikasi séjén
+
+ Anjeun bisa ninggalkeun %1$s pikeun muka ieu tutumbu di %2$s.
+
+ Téangan aplikasi nu bisa muka tutumbu
+
+ Euweuh aplikasi nu bisa muka ieu tutumbu. Anjeun bisa ninggalkeun %1$s pikeun maluruh aplikasi nu bisa di %2$s.
+
+ Tutup Langlangan Nyamuni?
+
+
+ %1$s anggeus
+
+
+ Buka
+
+
+
+
+
+
+
+
+
+
+ Ditambahkeun ka takulan!
+
+ Server teu kapanggih
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tutup
+
+
+
+ Wilujeng sumping di %1$s
+
+
+ Gancang. Pribadi. Teu élodan.
+
+
+ Mitembeyan
+
+
+
+ %1$s teu kawas panyungsi lianna
+
+
+ Kami mupus jujutan anjeun nalika aplikasi ditutup pikeun pripasi tambahan.
+
+
+
+ Jadikeun %1$s baku pikeun nangtayungan data anjeun ti unggal tutumbu anu dibuka.
+
+
+ Jadikeun panyungsi baku
+
+
+ Liwat
+
+
+
+ Bedaskeun salindungan anjeun
+
+ Cokot langlangan nyalindung ka undakan saterusna. Peungpeuk iklan jeung kontén lianna nu bisa ngalacak anjeun di sakuliah loka sarta ngalaunan waktu muka kaca.
+
+
+ Pamaluruhan anjeun, cara anjeun
+
+ Néangan nu béda? Pilih mesin pamaluruhan baku séjénna di Setélan.
+
+
+ Tambah takulan ka layar tepas
+
+ Sakocepat balik deui kana loka karesep anjeun %1$s . Pilih baé \"Tambahkeun ka layar Tepas\" tina menu %1$s.
+
+
+ Matuhkeun salindungan
+
+ Setél %1$s jadi panyungsi baku sarta cokot kauntungan langlangan nyalindung sawaktu anjeun muka kaca raramat ti aplikasi séjén.
+
+ Okéh, ngarti!
+ Liwat
+ Tuluy
+
+
+ -
+
+
+ Tambah
+
+ ENYA
+
+
+ Bolay
+
+ MOAL
+
+
+ Takulan bakal muka kalawan Kilung Palacakan Tingkat Lanjut pareum
+
+
+ Rintakan langlangan nyamuni
+
+
+ Iber pikeun ngahapus rintakan %1$s anjeun sakali tap. Anjeun teu kudu muka appna atawa nempo naon anu keur jalan dina panyungsi anjeun.
+
+
+ Pupus jujutan langlangan
+
+
+ Undeur Firefox
+
+
+
+
+
+
+
+
+ Lisénsi Publik Mozilla jeung lisénsi sumber nembrak lianna.]]>
+
+
+ di dieu.]]>
+
+
+ lisénsi bébas jeung sumber nembrak séjénna.]]>
+
+
+ GNU General Public License v3, sarta sayagadi dieu .]]>
+
+
+ Sandiasma
+ Kecap sandi
+ Beresihan
+
+
+
+ Sambungan aman
+ Sambungan teu aman
+
+ Dipéripikasi ku: %1$s
+
+
+ Kaamanan Loka
+ URL geus aya
+
+
+ Téangan dina Kaca
+
+
+ Téangan dina Kaca
+
+
+ %1$d/%2$d
+
+ %1$d ti %2$d
+
+
+ Téang hasil séjén
+
+ Téang hasil saméméhna
+
+ Tutup téangan dina kaca
+
+
+
+
+ Pénta loka déstop
+
+
+ Loka déstop
+
+
+ URL ditiron
+
+
+ Parabot pamekar
+
+
+ Buka tutumbu dina aplikasi
+
+
+ Leuwih lengkep
+
+
+ Idin loka
+
+
+ Kurangan Spanduk Réréméh
+
+
+ Hurung
+
+
+ Pareum
+
+
+ Kurangan Spanduk Réréméh
+
+
+ Kurangan spanduk ku otomatis nolak rekés réréméh, lamun bisa.
+
+ -->
+ Kurangan Spanduk Réréméh
+
+
+ Hurungkeun jang ieu loka
+
+
+ Kiwari loka teu didukung
+
+
+ Pareum jang ieu loka
+
+
+ Kurangan Spanduk Réréméh
+
+
+ Pareum jang ieu loka
+
+
+ Hurungkeun jang ieu loka
+
+
+ Hurungkeun Reduksi Spanduk Réréméh pikeun %1$s?
+
+
+ Pareuman Reduksi Spanduk Réréméh pikeun %1$s?
+
+
+ %1$s bakal mupus réréméh ieu loka tur muka ulang kacana. Ngaberesihan sadaya réréméh bisa ngaluarkeun anjeun atanapi ngosongkeun karanjang balanja.
+
+
+ %1$s bisa nyoba sacara otomatis nolak rekés réréméh.
+
+
+ Kiwari ieu loka teu didukung ku Reduksi Spanduk Réréméh. Anjeun rék rekés tim kami pikeun nénjo ieu raramatloka sarta nambahkeun pangrojong ka hareupna?
+
+
+ Bolay
+
+
+ Rekés dukungan
+
+
+ Rekés ka loka dukungan geus dikirim.
+
+
+ Rekés ka loka dukungan geus dikirim.
+
+
+
+ %1$s nyobaan nolak rekés réréméh pikeun ngudar spanduk réréméh anu ngaganggu.\n\nKokolakeun préperénsi spanduk réréméh dina %2$s.
+
+ setélan
+
+
+ Otomaén
+
+
+ Ngarah bisa:
+
+
+ 1. Buka Setélan Android
+
+
+ Idin]]>
+
+
+ Buka Setélan
+
+
+ %1$s ka ON]]>
+
+
+ Kaméra
+
+
+ Mikropon
+
+
+ Tempat
+
+
+ Iber
+
+
+ Kontén anu diatur ku DRM
+
+
+ Pénta idin
+
+
+ Dipeungpeuk
+
+
+ Diidinan
+
+
+ Dipeungpeuk ku Android
+
+
+ Idinan audio jeung pidéo
+
+
+ Peungpeuk audio hungkul
+
+
+ Disarankeun
+
+
+ Peungpeuk audio jeung pidéo
+
+
+ Studi
+
+
+ Firefox bisa masang jeung ngajalankeun studi iraha baé.
+
+
+ Lenyepan
+
+
+ Aplikasi bakal kaluar pikeun nerapkeun parobahan
+
+
+ Piceun
+
+
+ Aktip
+
+
+ Réngsé
+
+
+ Debugging ti kajauhan liwat USB/Wi-Fi
+
+
+ Buka konci
+
+
+ Kompirmasi maké Sidik Ramo Anjeun
+
+
+ Anjeun bisa maké sidik ramo pikeun neruskeun rintakan aplikasi ayeuna.
+
+
+ Buka Tutumbu dina Rintakan Anyar
+
+
+ Ikon sidik ramo
+
+
+ Sidik ramo teu dipikawanoh. Pecakan deui.
+
+
+ Napelkeun ramona téréh teuing. Pecakan deui.
+
+
+ Témbongkeun bongbolongan maluruh?
+
+
+ Pikeun nampa bongbolongan, %1$s kudu ngirim naon anu diketikkeun dina bilah alamat kana mesin panyungsi.
+
+
+ Moal
+
+
+ Enya
+
+
+ Sababaraha mesin panyungsi teu bisa némbongkeun saran.
+
+
+ Tutup
+
+
+
+
+ Lokana ngaco?\n Coba pareuman Salindung Palacak
+
+
+ Tambahkeun ka layar Tepas]]>
+
+
+ Buka sakur tutumbu di %1$s\n Setél %1$s minangka panyungsi baku
+
+
+ Otokumplit URLs pikeun loka anu mindeng dianjangan\n Pencét lila URL dina bilah alamat
+
+
+ Muka hiji tutumbu dina hiji tab anyar\n Pencét lila tutumbu dina kacana
+
+
+ Pareuman tips dina layar mimiti
+
+
+ Tab anyar dibuka
+
+
+ Gilir
+
+
+ Asup ka mode layar pinuh
+
+
+ Geuwat gilir ka tutumbu di tab anyar
+
+
+ Meungpeuk loka pibahyaeun jeung titipu
+
+ Peungpeuk loka anu dilaporkeun titipu jeung virusan, loka malwér, sarta loka sopwér nu teu dipiharep.
+
+
+ Mode Ngan-HTTPS
+
+
+ Otomatis nyoba nyambung ka loka maké protokol énkripsi HTTPS pikeun ngaronjatkeun kaamanan.
+
+
+ Iwal
+
+ Anjeun numpurkeun Peungpeuk Kontén pikeun ieu raramatloka.
+
+ Piceun
+
+ Piceun sakabéh raramatloka
+
+
+ Peungpeuk Réréméh
+
+
+ Badé meungpeuk réréméh?
+
+
+ Tab Ruksak
+
+ Hampura. Aya masalah dina ieu tab.
+
+ Salaku panyungsi nyamuni, tab teu kungsi disimpen sarta teu bisa dibuka ulang.
+
+ Tutup Tab
+
+
+
+
+
+ Kirim laporan nu ruksak ka Mozilla
+
+
+
+
+ Palacak diblokir ti %s
+
+ Eusi
+
+ Iklan
+
+ Sosial
+
+ Analitika
+
+ Protéksi Palacakan Tingkat Lanjut
+
+ Salindung PAREUM pikeun ieu loka
+
+ Perlindungan AKTIP pikeun ieu loka
+
+
+ Sambungan aman
+
+ Sambungan teu aman
+
+
+ Palacak jeung Skrip Peungpeukeun
+
+
+ Balik deui
+
+
+
+ Piceun
+
+
+ Ganti ngaran
+
+ Ganti ngaran
+
+
+ Ngaran takulan
+
+
+ Gambar teundeunan jeung nu dibagikeun mah <b>moal</b> dipupus nalika anjeun mupus %1$s jujutan
+
+
+
+ Téma
+
+ Caang
+
+ Poék
+
+ Atur ku Pangirit Batré
+
+ Tuturkeun téma alat
+
+
+
+ Ieu loka henteu ngadukung HTTPS
+
+
+ Lenyepan
+ Robah ieu setélan dina Setélan > Pripasi & Kaamanan > Kaamanan.]]>
+
+
+ Sambungan teu aman
+
+
+
+ Lamun saméméhna anjeun kungsi nyambung ka ieu serper, bisa jadi érorna samentara.
+ ]]>
+
+
+ Batur bisa nyobaan malsukeun lokana, mending ulah diteruskeun.
+
+ %1$s cangcaya ka %2$s alatan nu ngaluarkeun sértipikatna teu dipikawanoh, sértipikatna ditéken sorangan, atawa serperna teu ngirim sértipikat panengah anu bener. ]]>
+
+
+
+ Tutup tab
+
+
+
+ Beunang siah! Kami ngeureunan ieu loka tina moncorong anjeun. Toél taméng iraha baé pikeun nempo naon baé anu geus dipeungpeuk.
+
+
+ Tutup popup
+
+
+
+ Anjeun kajaga!
+
+
+ Setélan baku ieu nawarkeun panyalindungan anu kuat. Tapi gampang ngarobah setélan pikeun nyumponan pangabutuh anjeun.
+
+ Tutup
+
+
+ Toél di dieu pikeun mupus sakabéhna — jujutan, réréméh, sadayana — sarta mitembeyan dina hiji tab anyar.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Tutup
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Paluruh wijet
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Data sungsian geus beresih! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Mimitian rintakan pamaluruhan nyamuni anjeun, kami bakal meungpeuk palacak jeung nu garoréng lianna nalika anjeun miang.
+
+
+ Kami ninggalkeun anjeun dina ngalanglang nyamuni, tapi engké deui mah anjeun bisa ngamimitian leuwih gancang maké wijet %1$s dina layar Tepas.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Tambahkeun wijet ka layar tepas
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Wijet ditambahkeun kana layar tepas
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sv-rSE/strings.xml b/mobile/android/focus-android/app/src/main/res/values-sv-rSE/strings.xml
new file mode 100644
index 0000000000..2f1a33894b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sv-rSE/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ Avbryt
+
+ OK
+
+ Spara
+
+
+ Sök eller ange adress
+
+ Automatisk privat surfning.\nSurfa. Radera. Upprepa.
+
+
+ Din webbhistorik har raderats.
+ Webbhistoriken rensad
+
+
+ Flikens surfhistorik har raderats.
+
+
+ Sök efter %1$s
+
+
+ Dela…
+
+
+ Rapportera webbplatsproblem
+
+
+ Öppna med %1$s
+
+
+ Öppna med…
+
+
+ Lägg till på startskärmen
+
+
+ Lägg till i genvägar
+
+ Ta bort från genvägar
+
+
+ Inställningar
+ Om
+ Hjälp
+ Dina rättigheter
+
+
+ Trackers blockerade
+
+
+ Om du stänger av kan det fixa några webbplatsproblem
+
+
+ Innehållsblockering
+
+ Stäng av för att fixa vissa webbplatser
+
+
+ Levereras av %1$s
+
+
+ Dela via
+
+ Radera webbhistorik?
+ Tryck på eller rensa den här aviseringen för att säkert radera din webbhistorik.
+
+
+ Tryck på eller svep den här aviseringen för att säkert radera din webbhistorik.
+
+ Radera webbhistorik
+
+
+ Öppna
+
+
+ Radera och öppna
+
+
+ Radera
+
+
+ Radera webbhistorik
+
+
+
+ Radera & öppna
+
+
+ Radera och öppna %1$s
+
+
+
+ Sök i Focus
+
+ Sök i Klar
+
+ Sök i Focus Beta
+
+ Sök i Focus Nightly
+
+
+ %1$s ger dig full kontroll.
+Använd den som en privat webbläsare:
+
+ Sök och surfa direkt i appen
+ Blockera trackers (eller uppdatera inställningar att tillåta trackers)
+ Radera för att ta bort kakor samt sök- och webbhistorik
+
+
+%1$s är utvecklad av Mozilla. Vårt uppdrag är att främja ett hälsosamt, öppet Internet.
+Läs mer
]]>
+
+
+ Sekretess & säkerhet
+
+
+ Spårning, kakor, dataval
+
+
+ Ange standard, autokomplettera
+
+
+
+
+ Om %1$s, hjälp
+
+
+ Förbättrat spårningsskydd
+
+
+ Webbinnehåll
+
+
+ Växla appar
+
+
+ Allmänt
+
+
+ Standardwebbläsare, språk
+
+
+ Datainsamling & användning
+
+ Sök
+
+
+ Få sökförslag
+
+ %1$s skickar det du skriver i adressfältet till din sökmotor
+
+
+ Standard
+
+
+ Sökmotor
+
+
+ På
+
+
+ Av
+
+
+ Autokomplettera webbadress
+
+
+ För toppwebbplatser
+
+
+ Aktivera för att få %s att autokomplettera över 450 populära webbadresser i adressfältet.
+
+
+ För webbplatser du lägger till
+
+
+ Aktivera för att låta %s autokomplettera dina favoritwebbadresser.
+
+
+ Hantera webbplatser
+
+
+ Hantera webbplatser
+
+
+ + Lägg till anpassad webbadress
+
+
+ Din autokompletteringslista:
+
+
+ Lägg till URL
+
+
+ Lägg till anpassad webbadress
+
+
+ Lägg till anpassad webbadress
+
+
+ Lägg till länk till autokomplettering
+
+
+ Kakor och webbplatsdata
+
+
+ Dataval
+
+
+ Ta bort anpassade webbadresser
+
+
+ Läs mer
+
+
+ Lägg till och hantera anpassade autokompletterade webbadresser.
+
+
+ Webbadress att lägga till
+
+
+ Klistra in eller ange webbadress
+
+
+ Exempel: mozilla.org
+
+
+ Exempel: example.com
+
+
+ Ny anpassad webbadress tillagd.
+
+
+ Ta bort
+
+
+ Ta bort
+
+
+ Dubbelkolla webbadressen du angav.
+
+ Språk
+
+ Systemstandard
+
+ Sekretess
+ Blockera annons-trackers
+ Vissa annonser spårar dina besök på webbplatsen, även om du inte klickar på annonserna
+ Blockera analytiska trackers
+ Används för att samla in, analysera och mäta aktiviteter som tryckningar och rullningar
+ Blockera sociala trackers
+ Inbäddad på webbplatser för att spåra dina besök och visa funktionalitet som dela-knappar
+ Blockera andra innehållstrackers
+ Aktivering kan leda till att vissa sidor uppträder oväntat
+ Blockera kakor
+
+
+ Nej, tack
+ Blockera endast tredjepartspårarkakor
+ Blockera endast tredjepartskakor
+
+ Blockera globala kakor
+ Ja, tack
+
+
+ Använd fingeravtryck för att låsa upp appen
+
+
+ Lås upp med fingeravtryck om du har lagt till genvägar eller när en webbplats redan är öppen i %s.
+
+
+ Dolt läge
+
+ Dölj webbsidor när du byter appar och blockerar skärmdumpar.
+
+ Säkerhet
+
+ Prestanda
+ Blockera webbtypsnitt
+
+ Kan resultera i att ikoner eller bilder saknas
+
+ Blockera Javascript
+
+ Sidor kan laddas snabbare, men kan också uppträda oväntat
+
+
+ Gör %1$s till standardwebbläsare
+
+ Mozilla
+ Skicka användningsdata
+
+
+ Läs mer
+
+
+ Mozilla strävar efter att endast samla in det vi behöver för att tillhandahålla och förbättra %1$s för alla.
+
+
+ Sekretesspolicy
+
+
+ Licensinformation
+
+
+ Bibliotek som vi använder
+
+
+ %s | OSS-bibliotek
+
+
+ Om %1$s
+
+
+ Installerade sökmotorer
+
+
+ Välj sökmotor
+
+
+ Återställ standardsökmotorer
+
+
+ + Lägg till en annan sökmotor
+ Ta bort sökmotorer
+ Ta bort
+
+
+ Lägg till en annan sökmotor
+
+ Välj din önskade sökmotor:
+
+
+ Lägg till sökmotor
+
+ Sökmotorns namn
+ Söksträng som ska användas
+ Spara
+
+
+ Exempel: example.com/search/?q=%s
+
+ Ny sökmotor tillagd.
+
+ Ange sökmotorns namn
+ En installerad sökmotor använder redan det namnet.
+
+ Ange söksträng
+
+ Kontrollera att söksträngen matchar exempelformat
+
+
+ Rensa inmatning
+
+
+ Ignorera
+
+
+ Radera webbhistorik
+
+
+ Flikar öppna: %1$s
+
+
+ Säker anslutning
+
+
+ Laddar
+
+
+ Webbplats laddad
+
+
+ Fler alternativ
+
+
+ Knappen fler alternativ
+
+
+ Navigera framåt
+
+
+ Ladda om webbplats
+
+
+ Navigera bakåt
+
+
+ Sluta ladda webbplats
+
+
+ Återgå till föregående app
+
+
+ Antal trackers blockerade
+
+
+ Blockera trackers
+
+ Dina rättigheter
+
+ Öppna länk i en annan app
+
+ Du kan lämna %1$s för att öppna denna länk i %2$s.
+
+ Hitta en app som kan öppna länken
+
+ Ingen av apparna på din enhet kan öppna denna länk. Du kan lämna %1$s för att söka i %2$s efter en app som kan göra detta.
+
+ Avsluta Privat surfning?
+
+
+ %1$s slutförd
+
+
+ Öppna
+
+
+
+
+
+
+
+
+
+
+ Tillagd till genvägar!
+
+ Servern hittades inte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stäng
+
+
+
+ Välkommen till %1$s
+
+
+ Snabb. Privat. Inga distraktioner.
+
+
+ Kom igång
+
+
+
+ %1$s är inte som andra webbläsare
+
+
+ Vi rensar din historik när du stänger appen för extra sekretess.
+
+
+
+ Gör %1$s till din standard för att skydda dina data med varje länk du öppnar.
+
+
+ Ange som standardwebbläsare
+
+
+ Hoppa över
+
+
+
+ Stärk din integritet
+
+ Ta privat surfning till nästa nivå. Blockera annonser och annat innehåll som kan spåra dig på webbplatser och slöa ner laddningen av webbsidor.
+
+
+ Sök som du vill
+
+ Letar du efter något annat? Välj en annan standardsökmotor i Inställningar.
+
+
+ Lägg till genvägar till startskärmen
+
+ Återgå till dina favoritwebbplatser i %1$s snabbt. Välj \"Lägg till på startskärmen\" från menyn %1$s.
+
+
+ Låt integritet bli en vana
+
+ Ange %1$s som standardwebbläsare och dra nytta av fördelarna med privat surfning när du öppnar webbsidor från andra appar.
+
+ OK, jag förstår!
+ Hoppa över
+ Nästa
+
+
+ -
+
+
+ Lägg till
+
+
+ JA
+
+
+ Avbryt
+
+
+ NEJ
+
+
+ Genvägen öppnas med förbättrat spårningsskydd inaktiverat
+
+
+ Privat surfsession
+
+
+ Med aviseringar kan du radera din session i %1$s med ett klick. Du behöver inte öppna appen eller se vad som körs i din webbläsare.
+
+
+ Radera webbhistorik
+
+
+ Hämta Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License och andra open source-licenser.]]>
+
+
+ här.]]>
+
+
+ licenser.]]>
+
+
+ GNU General Public License v3 och tillgänglig här .]]>
+
+
+ Användarnamn
+ Lösenord
+ Rensa
+
+
+
+ Säker anslutning
+ Osäker anslutning
+
+ Verifierad av: %1$s
+
+
+ Webbplatssäkerhet
+ URL finns redan
+
+
+ Hitta på sidan
+
+
+ Hitta på sidan
+
+
+ %1$d/%2$d
+
+ %1$d av %2$d
+
+
+ Hitta nästa resultat
+
+ Hitta föregående resultat
+
+ Avvisa hitta på sidan
+
+
+
+
+ Begär datorwebbplats
+
+
+ Webbplats för datorer
+
+
+ URL kopierad
+
+
+ Utvecklarverktyg
+
+
+ Öppna länkar i appar
+
+
+ Avancerat
+
+
+ Webbplatsbehörigheter
+
+
+ Reducering av kakbanner
+
+
+ På
+
+
+ Av
+
+
+ Reducering av kakbanner
+
+
+ Se färre banners genom att automatiskt avvisa kak-förfrågningar, när det är möjligt.
+
+ -->
+ Reducering av kakbanner
+
+
+ PÅ för denna webbplats
+
+
+ Webbplatsen stöds inte för närvarande
+
+
+ AV för denna webbplats
+
+
+ Reducering av kakbanner
+
+
+ AV för denna webbplats
+
+
+ PÅ för denna webbplats
+
+
+ Vill du aktivera reducering av kakbanners för %1$s?
+
+
+ Vill du stänga av reducering av kakbanners för %1$s?
+
+
+ %1$s rensar webbplatsens kakor och uppdaterar sidan. Rensa alla kakor kan logga ut dig eller tömma kundvagnar.
+
+
+ %1$s kan försöka att automatiskt avvisa kak-förfrågningar.
+
+
+ Denna webbplats stöds för närvarande inte av Reducering av kakbanner. Vill du be vårt team granska denna webbplats och lägga till support i framtiden?
+
+
+ Avbryt
+
+
+ Begär support
+
+
+ Begäran om supportwebbplats har skickats.
+
+
+ Begäran om supportwebbplats har skickats.
+
+
+
+ %1$s försöker avvisa kak-förfrågningar för att avvisa irriterande kakbanners.\n\nHantera kakbannerinställningar i %2$s.
+
+
+ inställningar
+
+
+ Automatisk uppspelning
+
+
+ För att tillåta det:
+
+
+ 1. Gå till Android-inställningar
+
+
+ Behörigheter]]>
+
+
+ Gå till Inställningar
+
+
+ %1$s till PÅ]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Plats
+
+
+ Avisering
+
+
+ DRM-kontrollerat innehåll
+
+
+ Fråga för att tillåta
+
+
+ Blockerad
+
+
+ Tillåten
+
+
+ Blockerad av Android
+
+
+ Tillåt ljud och video
+
+
+ Blockera endast ljud
+
+
+ Rekommenderad
+
+
+ Blockera ljud och video
+
+
+ Studier
+
+
+ Firefox kan installera och köra studier då och då.
+
+
+ Läs mer
+
+
+ Applikationen avslutas för att tillämpa ändringar
+
+
+ Ta bort
+
+
+ Aktiv
+
+
+ Slutförd
+
+
+ Fjärrfelsökning via USB/Wi-Fi
+
+
+ Lås upp
+
+
+ Bekräfta genom att använda ditt fingeravtryck
+
+
+ Du kan använda ditt fingeravtryck för att fortsätta din nuvarande appsession.
+
+
+ Öppna länk i ny session
+
+
+ Fingeravtrycksikon
+
+
+ Fingeravtryck känns inte igen. Försök igen.
+
+
+ Fingret rörde sig för fort. Försök igen.
+
+
+ Visa sökförslag?
+
+
+ För att få förslag måste %1$s skicka det du skriver i adressfältet till sökmotorn.
+
+
+ Nej
+
+
+ Ja
+
+
+ Vissa sökmotorer kan inte visa förslag.
+
+
+ Ignorera
+
+
+
+
+ Beter sig webbplatsen konstigt?\n Försök att stänga av spårningsskydd
+
+
+ Lägg till på startsidan]]>
+
+
+ Öppna varje länk i %1$s\n Ange %1$s som standardwebbläsare
+
+
+ Autokomplettera webbadresser för webbplatser som du använder mest\n Tryck länge på vilken URL som helst i adressfältet
+
+
+ Öppna en länk i en ny flik\n Tryck länge på en länk på en sida
+
+
+ Stäng av tips på startskärmen
+
+
+ Ny flik öppnad
+
+
+ Växla
+
+
+ Startar fullskärmsläge
+
+
+ Växla till länk i ny flik omedelbart
+
+
+ Blockera potentiellt farliga och vilseledande webbplatser
+
+ Blockera rapporterade vilseledande webbplatser och de som angriper dig, distribuerar skadlig kod eller oönskad programvara.
+
+
+ Endast HTTPS-läge
+
+
+ Försöker automatiskt ansluta till webbplatser med HTTPS-krypteringsprotokollet för ökad säkerhet.
+
+
+ Undantag
+
+ Du har inaktiverat innehållsblockering för dessa webbplatser.
+
+ Ta bort
+
+ Ta bort alla webbplatser
+
+
+ Blockera kakor
+
+
+ Vill du blockera kakor?
+
+
+ Fliken kraschade
+
+ Förlåt. Vi har ett problem med den här fliken.
+
+ Som en privat webbläsare sparar vi aldrig och kan inte återställa den här fliken.
+
+ Stäng flik
+
+
+
+
+
+ Skicka kraschrapport till Mozilla
+
+
+
+
+ Spårare blockerade sedan %s
+
+ Innehåll
+
+ Reklam
+
+ Socialt
+
+ Statistik
+
+ Förbättrat spårningsskydd
+
+ Skydd är AV för den här webbplatsen
+
+ Skydd är PÅ för den här webbplatsen
+
+ Anslutningen är säker
+
+ Anslutningen är inte säker
+
+
+ Spårare och skript att blockera
+
+
+ Gå tillbaka
+
+
+
+ Ta bort
+
+
+ Byt namn
+
+ Byt namn
+
+
+ Genvägens namn
+
+
+ Sparade och delade bilder <b>raderas inte</b> när du rensar historiken för %1$s
+
+
+
+ Tema
+
+ Ljust
+
+ Mörkt
+
+ Bestämt av energisparläge
+
+ Följ enhetens tema
+
+
+
+ Den här webbplatsen stöder inte HTTPS
+
+
+ Läs mer
+ Ändra den här inställningen i Inställningar > Sekretess & Säkerhet > Säkerhet.]]>
+
+
+ Anslutningen är inte säker
+
+
+
+ Om du har anslutit till den här servern tidigare kan felet vara tillfälligt.
+ ]]>
+
+
+ Någon kan försöka utge sig för webbplatsen och att fortsätta kan vara riskabelt.
+
+ %1$s litar inte på %2$s eftersom dess certifikatutfärdare är okänd, certifikatet är självsignerat eller att servern inte skickar rätt mellanliggande certifikat.
+ ]]>
+
+
+
+ Stäng flik
+
+
+
+ Fick dem! Vi stoppade den här sidan från att spionera på dig. Tryck på skölden när som helst för att se vad vi blockerar.
+
+
+ Stäng popup
+
+
+
+ Du är skyddad!
+
+ Dessa standardinställningar erbjuder ett starkt skydd. Men det är lätt att justera inställningarna för att möta dina specifika behov.
+
+ Ignorera
+
+
+ Tryck här för att kasta allt — historik, kakor, allt — och börja om på en ny flik.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Stäng
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Sök-widget
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Webbhistorik rensad! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Starta din privata surfsession och vi kommer att blockera spårare och andra dåliga saker medan du surfar.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Vi lämnar dig till din privata surfning, men få en snabbare start nästa gång med %1$s-widgeten på din startskärm.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Lägg till widget på startskärmen
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget har lagts till på startskärmen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sw480dp/dimens.xml b/mobile/android/focus-android/app/src/main/res/values-sw480dp/dimens.xml
new file mode 100644
index 0000000000..e2e81baf13
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sw480dp/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 256dp
+ 384dp
+ 256dp
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-sw600dp/dimens.xml b/mobile/android/focus-android/app/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000000..3a98077f71
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 320dp
+ 480dp
+ 320dp
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ta/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000000..f7f8229923
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ta/strings.xml
@@ -0,0 +1,552 @@
+
+
+
+
+
+
+
+
+ ரத்து
+
+ சரி
+
+ சேமி
+
+
+ தேட (அ) உலாவ முகவரியை உள்ளிடுக
+
+ தானியக்க அந்தரங்க உலாவல்.\nஉலாவு. அழி. திரும்பச்செய்.
+
+
+ உங்கள் உலாவல் வரலாறு அழிக்கப்பட்டது.
+ உலாவி வரலாறு அழிக்கப்பட்டது
+
+
+ கீற்றின் உலாவல் வரலாறு அழிக்கப்பட்டது.
+
+
+ %1$s சொல்லைத் தேடு
+
+
+ பகிர்…
+
+
+ தள சிக்கலைத் தெரிவி
+
+
+ %1$s உலாவியில் திற
+
+
+ உலாவியில் திற…
+
+
+ முகப்பு திரைக்கு சேர்
+
+
+ குறுக்குவழி சேர்க்கவும்
+
+ குறுக்குவழி நீக்கு
+
+
+ அமைவுகள்
+ போக்கசு பற்றி
+ உதவி
+ உரிமைகள்
+
+
+ பின்தொடரி
+
+
+ இதை முடக்குவது சில இணையதளச் சிக்கல்களைச் சரிசெய்யலாம்
+
+
+ உள்ளடக்க தடுப்பு
+
+ சில இணையதளங்களைச் சரிசெய்ய அணைக்கவும்
+
+
+ %1$s மூலம் இயக்கப்படுகிறது
+
+
+ தோன்றும் செயலிகளின் வழி பகிர்க
+
+
+ உலாவல் வரலாற்றை அழி
+
+
+ திற
+
+
+ துடைத்து திற
+
+
+ அழி
+
+
+ உலாவல் வரலாற்றை அழி
+
+
+
+ துடைத்துத் திறக்கவும்
+
+
+ %1$s ஐ துடைத்துத் திறக்கவும்
+
+
+ ஃபோகசில் தேடு
+
+ கிளாரில் தேடு
+
+ ஃபோகசு பீட்டாவில் தேடு
+
+ ஃபோகசு நைட்லியில் தேடு
+
+
+ %1$s கட்டுப்பாட்டை உங்களிடம் வழங்குகிறது.
+அதை ஒரு தனிப்பட்ட உலாவியாக பயன்படுத்துங்கள்:
+
+செயலியினுள் தேடுங்கள் உலாவுங்கள்
+தடமிகளை முடக்குங்கள் (அல்லது தடமிகளை அனுமதிக்க அமைப்புகளைப் புதுப்பியுங்கள்)
+குக்கிகள், தேடல் மற்றும் உலாவல் வரலாற்றை அழியுங்கள்
+
+
+%1$s Mozilla ஆல் உருவாக்கப்படுகிறது. ஆரோக்கியமான, திறந்த இணையத்தை வளர்த்தெடுப்பதே எங்களின் இலக்கு ஆகும்.
+மேலும் அறிய
]]>
+
+
+ தனியுரிமை மற்றும் பாதுகாப்பு
+
+
+ தடமறிதல், நினைவிகள், தரவு தேர்வுகள்
+
+
+ தானிரப்பியை இயல்பாக அமை
+
+
+
+
+ %1$s பற்றிய உதவி
+
+
+ மேம்படுத்தப்பட்ட தடமறி பாதுகாப்பு
+
+
+ வலை உள்ளடக்கம்
+
+
+ செயலிகள் இடமாற்றம்
+
+
+ பொது
+
+
+ இயல்புநிலை உலாவி, மொழி
+
+
+ தரவு சேகரிப்பு மற்றும் பயனளவு
+
+ தேடல்
+
+
+ தேடல் பரிந்துரைகளைக் காட்டு
+
+
+ முன்னிருப்பு
+
+
+ இயக்கு
+
+
+ அணை
+
+
+ URL தானியங்கு நிரப்பு
+
+
+ + தனிபயன் URL சேர்
+
+
+ தனிபயன் URL சேர்
+
+
+ தனிபயன் URL சேர்
+
+
+ நினைவிகள் மற்றும் தள தரவு
+
+
+ தரவு தெரிவுகள்
+
+
+ தனிபயன் URLs நீக்கு
+
+
+ மேலும் அறிய
+
+
+ தனிபயன் தானியங்குநிரப்பு URLs சேர்த்து நிர்வகி.
+
+
+ சேர்க்க வேண்டிய URL
+
+
+ URL ஒட்டு அல்லது உள்ளிடு
+
+
+ எ.கா: mozilla.org
+
+
+ எகா: example.com
+
+
+ புது தனிபயன் URL சேர்க்கப்பட்டது.
+
+
+ நீக்கு
+
+
+ நீக்கு
+
+
+ நீங்கள் உள்ளிட்ட URL இருமுறை சரிபார்க்கவும்.
+
+ மொழி
+
+ கைப்பேசி முன்னிருப்பு
+
+ தனியுரிமை
+ விளம்பர பின்தொடரிகளைத் தடு
+ சில விளம்பரங்கள், நீங்கள் அதனை சொடுக்காவிட்டாலும் தள பார்வைகளைப் பின்தொடரும்
+ பகுப்பாய்வு பின்தொடரிகளைத் தடு
+ தட்டல், உருட்டுதல் போன்ற செயல்களைச் சேகரிக்கும், ஆய்வுசெய்யும் அளவிடும்
+ சமூக பின்தொடரிகளைத் தடு
+ உங்கள் பார்வைகளைப் பின்தொடரவும் பகிர்தல் பொத்தான் போன்றவற்றைக் காட்டவும் தளத்தில் உட்பொதியப்பட்டிருக்கும்
+ உள்ளடக்க பின்தொடரிகளைத் தடு
+ செயற்படுத்துவது சில பக்கங்கள் எதிர்பாராமல் தவறாகச் செயல்பட காரணமாகலாம்
+ நினைவிகளை முடக்கு
+
+ 3 ஆம் தரப்பு நினைவிகளை மட்டும் முடக்கு
+
+
+ போரியல்
+
+ செயலிகள் மாறும்பொழுது வலைப்பக்கங்களை மறை மற்றும் திரைப்பிடிப்பு எடுப்பதைத் தடு.
+
+ செயல்திறன்
+ இணைய எழுத்துருக்களைத் தடு
+
+ சின்னம் (அ) படங்கள் காணாமல் போகலாம்
+
+ ஜாவாஸ்கிரிப்டை முடக்கு
+
+ பக்கம் வேகமாக ஏறும், ஆனால் எதிர்பாரா விதத்தில் செயல்படலாம்
+
+
+ %1$s உங்கள் முன்னிருப்பு உலாவியாக மாற்றவும்
+
+ மொசில்லா
+ பயனளவு தரவை அனுப்பு
+
+
+ மேலும் அறிய
+
+
+ அனைவருக்கும் %1$s வழங்க மற்றும் மேம்படுத்த தேவையானதை மட்டும் சேகரிக்க மொசில்லா உறுதிபூண்டுள்ளது.
+
+
+ தனியுரிமை கொள்கை
+
+
+ %1$s பற்றி
+
+
+ நிறுவப்பட்ட தேடு பொறிகள்
+
+
+ முன்னிருப்பு தேடுபொறிகளை மீட்டமை
+
+
+ + மற்றொரு தேடு பொறியைச் சேர்
+ தேடு பொறிகளை நீக்கு
+ நீக்கு
+
+
+ தேடுபொறியைச் சேர்
+
+ தேடுபொறி பெயர்
+ பயன்படுத்த வேண்டிய தேடல் தொடர்
+ சேமி
+
+
+ எகா: example.com/search/?q=%s
+
+ புது தேடுபொறி சேர்க்கப்பட்டது.
+
+ தேடுபொறி பெயரை உள்ளிடு
+ நிறுவப்பட்ட தேடுபொறி ஏற்கனவே அப்பெயரைப் பயன்படுத்துகிறது.
+
+ தேடல் தொடரை உள்ளிடு
+
+ தேடல் தொடர் எகா வடிவத்தை ஒத்திருக்கிறதா என்று பார்
+
+
+ துள்ளிய உள்ளீடு
+
+
+ நிராகரி
+
+
+ உலாவல் வரலாற்றை அகற்று
+
+
+ திறந்த கீற்றுகள்: %1$s
+
+
+ பாதுகாப்பான இணைப்பு
+
+
+ ஏற்றுகிறது
+
+
+ வலையத்தளம் ஏற்றப்பட்டது
+
+
+ கூடுதல் தேர்வுகள்
+
+
+ வழிசெலுத்தி முன்செல்
+
+
+ வலைத்தளத்தை மீளேற்று
+
+
+ வழிசெலுத்தி பின்செல்
+
+
+ வலைத்தளம் ஏற்றுவதை நிறுத்து
+
+
+ முந்தைய செயலியிக்கு திரும்பு
+
+
+ தடமறிகளைத் தடு
+
+ உங்கள் உரிமைகள்
+
+ தொடுப்பை இன்னொரு செயலியில் திறக்கும்
+
+ %1$s உலாவியை விட்டு விலகி %2$s செயலியில் திறக்கும்.
+
+ தொடுப்பை திறக்கவல்ல ஒரு செயலியைக் கண்டுபிடி
+
+ இந்தச் சாதனத்தில் உள்ள எந்த ஒரு செயலியும் இத்தொடுப்பை திறக்கவல்லது. %1$s என்பதை விடுத்து ஒரு செயலியால் %2$s என்பதை தேட முடியும்.
+
+ அந்தரங்க உலாவலில் இருந்து வெளியேறவா?
+
+
+ %1$s முடிந்தது
+
+
+ திற
+
+
+
+
+
+
+
+
+
+ சேவகன் கிடைக்கவில்லை
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ தனியுரிமையைத் துரிதப்படுத்துங்கள்
+
+ தனிப்பட்ட உலாவலை அடுத்த நிலைக்கு எடுத்துச் செல்லவும். தளங்கள் எங்கும் உங்களைப் பின்தொடரக்கூடிய மற்றும் பக்க ஏற்று நேரத்தைக் கூட்டும் விளம்பரம் மற்றும் மற்ற உள்ளடக்கங்களை முடக்கு.
+
+
+ உங்களின் தேடல் உங்கள் வழி
+
+ வித்தியாசமானதைத் தேடுகிறீர்களா? அமைப்புகளில் வேறொரு இயல்புநிலை தேடியியந்திரத்தைத் தேர்ந்தெடுக்கவும்.
+
+
+ உங்கள் முகப்பு திரையில் சுருக்கு வழிகளைச் சேர்க்கவும்
+
+ %1$s உலாவியல் உங்கள் விருப்ப தங்களுக்கு விரைவாகத் திரும்பவும். %1$s மெனுவிலிருந்து \"முகப்பு திரையில் சேர்க்கவும்\" என்பதைத் தேர்ந்தெடுக்கவும்.
+
+
+ தனியுரிமையை ஒரு பழக்கமாக்கவும்
+
+ %1$s உலாவியை முன்னிருப்பாக அமைப்பதன் மூலம் பிற செயலிகளிலிருந்து வலைத்தளங்களைத் திறக்கும்போது அந்தரங்க உலாவலின் நன்மையைப் பெறலாம்.
+
+ சரி, புரிந்தது!
+ தாவு
+ அடுத்தது
+
+
+ -
+
+
+ சேர்
+
+ ஆம்
+
+
+ ரத்து
+
+ இல்லை
+
+
+ அந்தரங்க உலாவல் அமர்வு
+
+
+ அறிவிப்புகள் உங்கள் %1$s அமர்வை ஒரு தட்டில் அழிக்க அனுமதிக்கிறது. நீங்கள் செயலியைத் திறக்கவோ உலாவியில் என்ன இருக்கிறது என்று பார்க்கவோ தேவையில்லை.
+
+
+ உலாவல் வரலாற்றை அழி
+
+
+ பயர்பாக்சைப் பதிவிறக்கு
+
+
+
+
+
+
+
+
+ பயனர்பெயர்
+ கடவுச்சொல்
+ துடை
+
+
+
+ பாதுகாப்பான இணைப்பு
+ பாதுகாப்பற்ற இணைப்பு
+
+ சரிபார்க்கப்பட்டது: %1$s
+
+
+ தள பாதுகாப்பு
+ URL ஏற்கனவே உள்ளது
+
+
+ பக்கத்தில் கண்டுபிடி
+
+
+ பக்கத்தில் கண்டுபிடி
+
+
+ %1$d/%2$d
+
+ %2$d இல் %1$d
+
+
+ அடுத்த முடிவைக் கண்டுபிடி
+
+ முந்தைய முடிவைக் கண்டுபிடி
+
+ பக்கத்தில் கண்டவற்றை நிராகரி
+
+
+
+
+ கணினி பக்கத்தைக் கேள்
+
+
+ தொடுப்பு நகலெடுக்கப்பட்டது
+
+
+
+
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-te/strings.xml b/mobile/android/focus-android/app/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000000..ffb0dbed30
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-te/strings.xml
@@ -0,0 +1,666 @@
+
+
+
+
+
+
+
+
+ రద్దుచేయి
+
+ సరే
+
+ భద్రపరుచు
+
+
+ వెతకండి లేదా చిరునామాను ఇవ్వండి
+
+ నిరంతర అంతరంగిక విహారణ. విహరించండి. తుడిచివేయండి. మళ్ళీ మళ్ళీ.
+
+
+ మీ విహారణ చరిత్ర తుడిచివెయ్యబడింది.
+
+ విహారిణి చరిత్ర తుడిచివేయబడింది
+
+
+ %1$s కొరకు వెతకండి
+
+
+ పంచుకోండి…
+
+
+ సైటు సమస్యను నివేదించండి
+
+
+ %1$s లో తెరువు
+
+
+ తెరచు చోటు…
+
+
+ ముంగిలి తెరకు చేర్చు
+
+ అమరికలు
+ గురించి
+ సహాయం
+ మీ హక్కులు
+
+
+ నిరోధించిన ట్రాకర్లు
+
+
+ దీన్ని అచేతనం చేయడం కొన్ని సైట్ల సమస్యలు పరిష్కరించవచ్చు
+
+
+ విషయ నిరోధం
+
+
+ కొన్ని సైట్లను పరిష్కరించడానికి దీనిని అచేతనం చేయండి
+
+
+ వీరిచే %1$s
+
+
+ దీనిద్వారా పంచుకోండి
+
+
+ విహరణ చరిత్రను తుడిచివేయి
+
+
+ తెరువు
+
+
+ తుడిచివేసి తెరువు
+
+
+ తుడిచివేయి
+
+
+ విహరణ చరిత్రను తుడిచివేయి
+
+
+
+ అంతరంగికత & భద్రత
+
+
+ ట్రాకింగ్, కుక్కీలు, డేటా ఎంపికలు
+
+
+
+
+ %1$s గురించి, సహాయం
+
+
+ వెబ్ కంటెంట్
+
+
+ అనువర్తనాలు మార్చడం
+
+
+ సాధారణం
+
+
+ అప్రమేయ విహారిణి, భాష
+
+
+ డేటా సేకరణ మరియు ఉపయోగం
+
+ వెతుకు
+
+
+ వెతుకుడు సలహాలను చూపించు
+
+
+ మీరు చిరునామా పట్టీలో టైపుచేసినదాన్ని %1$s మీ సెర్చింజనుకి పంపిస్తుంది
+
+
+ అప్రమేయం
+
+
+ శోధన యంత్రం
+
+
+ చేతనం
+
+
+ అచేతనం
+
+
+ URL స్వయంపూర్తి
+
+
+ మేటి సైట్లకు
+
+
+ మీరు జోడించిన సైట్లకు
+
+
+ సైట్ల నిర్వహణ
+
+
+ సైట్లను నిర్వహించండి
+
+
+ +ఇష్టానుసారమైన URLను జోడించండి
+
+
+ ఇష్టానుసారమైన URLను జోడించండి
+
+
+ ఇష్టానుసారమైన URLను జోడించండి
+
+
+ లంకెను స్వయంపూరణకు చేర్చు
+
+
+ కుకీలు మరియు సైటు డేటా
+
+
+ దత్తాంశం ఎంపికలు
+
+
+ ఇష్టానుసారమైన URLs ను నిర్మూలించండి
+
+
+ ఇంకా తెలుసుకోండి
+
+
+ అభిమత స్వయంపూరణ చిరునామాలను చేర్చండి, నిర్వహించండి.
+
+
+ జోడించుటకు URL
+
+
+ URL అతికించండి లేదా ఇవ్వండి
+
+
+ ఉదాహరణ: mozilla.org
+
+
+ ఉదాహరణ: example.com
+
+
+ కొత్త అభిమత చిరునామా చేర్చబడింది.
+
+
+ తీసివేయి
+
+
+ తీసివేయి
+
+
+ మీరు ఇచ్చిన చిరునామాను మరోసారి సరిచూడండి.
+
+ భాష
+
+ వ్యవస్థ అప్రమేయం
+
+ అంతరంగికత
+ యాడ్ ట్రాకర్లను నిరోధించు
+ వైశ్లేషిక ట్రాకర్లను నిరోధించు
+ సామాజిక ట్రాకర్లను నిరోధించు
+ ఇతర ట్రాకర్లను నిరోధించు
+ కుకీలను నిరోధించాలా
+
+
+ వద్దు పర్లేదు
+ 3వ-పక్ష ట్రాకర్ కుకీలను మాత్రమే నిరోధించు
+ 3-వ పక్షం కుకీలను మాత్రమే నిరోధించు
+
+ క్రాస్-సైటు కుకీలను నిరోధించు
+
+
+ అనువర్తనపు తాళం తీయడానికి వేలిముద్రను వాడు
+
+
+ గోప్యత
+
+ యాప్స్ మధ్య మారేప్పుడు జాలపేజీలు కనబడకుండా దాచిపెట్టు, తెరపట్టు తీసుకోవడం నిరోధించు.
+
+ భద్రత
+
+ పనితనం
+ జాల ఖతులను (వెబ్ ఫాంట్లను) నిరోధించు
+
+ దీనివల్ల ప్రతీకాలు లేదా బొమ్మలు కనబడకపోవచ్చు
+
+ జావాస్క్రిప్టును నిరోధించు
+
+
+ పేజీలు వేగంగా తెరుచుకోవచ్చు, కానీ అనూహ్యంగా ప్రవర్తించవచ్చు కూడా
+
+
+ %1$sను అప్రమేయ విహారిణి చేయి
+
+ Mozilla
+ వాడుక దత్తాంశాన్ని పంపించు
+
+
+ ఇంకా తెలుసుకోండి
+
+
+ %1$sని అందరికీ అందించడానికి, మెరుగుపరచడానికి ఎంత తక్కవ అవసరమో అంతే సేకరించడానికి మొజిల్లా కృషిచేస్తుంది.
+
+
+ గోప్యతా విధానం
+
+
+ %1$s గురించి
+
+
+ స్థాపిత శోధన యంత్రాలు
+
+
+ అప్రమేయ శోధన యంత్రాలను పునరుద్ధరించు
+
+
+ + మరొక సెర్చింజనును చేర్చు
+ శోధన యంత్రమును తొలగించు
+ తీసివేయి
+
+
+ శోధన యంత్రాన్ని చేర్చు
+
+ సెర్చింజను పేరు
+ ఉపయోగించడానికి శోధన వాక్యము
+ భద్రపరచు
+
+
+ ఉదాహరణ: example.com/search/?q=%s
+
+ క్రొత్త శోధనాయంత్రం చేర్చబడినది.
+
+ శోధన యంత్రం పేరును నమోదు చెయ్యండి
+ ఒక్త నిక్షిప్తం చేయబడిన శోధన యంత్రం అదే పేరుతో ఉంది.
+
+ శోధన వాక్యమును నమోదు చేయండి
+
+ శోధన వాక్యము ఉదాహరణ ఆకృతిలో ఉందో లేదో చూడండి
+
+
+ ఇన్పుట్ను తుడిచివేయి
+
+
+ విస్మరించు
+
+
+ విహరణ చరిత్రను తుడిచేయి
+
+
+ తెరిచివున్న ట్యాబులు: %1$s
+
+
+ సురక్షిత అనుసంధానం
+
+
+ వస్తోంది
+
+
+ వెబ్సైటు లోడయింది
+
+
+ మరిన్ని ఎంపికలు
+
+
+ మరిన్ని ఎంపికల బొత్తం
+
+
+ ముందుకు వెళ్ళు
+
+
+ వెబ్సైటుని మళ్ళీ లోడుచేయి
+
+
+ వెనక్కి వెళ్ళు
+
+
+ వెబ్సైటు లోడుచేయడం ఆపివేయి
+
+
+ మునుపటి అనువర్తనానికి తిరిగి వెళ్ళు
+
+
+ నిరోధించిన ట్రాకర్ల సంఖ్య
+
+
+ ట్రాకర్లను నిరోధించండి
+
+ మీ హక్కులు
+
+ ఈ లంకెని మరొక అనువర్తనంలో తెరువు
+
+ %2$sని తెరువుటకు %1$sని వదలవచ్చును.
+
+ లింక్ను తెరవగల అనువర్తనాన్ని కనుగొనండి
+
+ మీ పరికరంలోని అనువర్తనాల్లో ఏదీ ఈ లింక్ను తెరవలేకపోతుంది. మీరు చేయగల అనువర్తనం కోసం %2$sను శోధించడానికి %1$sను వదిలివేయవచ్చు.
+
+ అంతరంగిక విహరణను చాలించాలా?
+
+
+ %1$s పూర్తయింది
+
+
+ తెరువు
+
+
+
+
+
+
+
+
+
+ సర్వర్ కనుగొనలేదు
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ మీ ప్రైవసీని శక్తిమంతం చేసుకోండి
+
+
+ మీ శోధన, మీ మార్గం
+
+ విభిన్నమైన వాటి కోసం శోధిస్తున్నారా? అమరికాలలో మరొక అప్రమేయ శోధన యంత్రంను ఎంచుకోండి.
+
+
+ మీ ముంగిలి తెరలో సత్వరమార్గాలను జోడించండి
+
+
+ ఆంతరంగికతను అలవాటు చేసుకోండి
+
+ సరే, అర్థమయ్యింది!
+ దాటు
+ తరువాత
+
+
+ -
+
+
+ చేర్చు
+
+
+ అవును
+
+
+ రద్దుచేయి
+
+
+ వద్దు
+
+
+ అంతరంగిక విహరణ సెషను
+
+
+ విహరణ చరిత్రను తుడిచివేయి
+
+
+ Firefoxను దింపుకోండి
+
+
+
+
+
+ వాడుకరి పేరు
+ సంకేతపదం
+ తుడిచివేయి
+
+
+
+ సురక్షిత అనుసంధానం
+ అరక్షిత అనుసంధానం
+
+ దీనిచేత నిర్ధారించబడింది: %1$s
+
+
+ సైటు భద్రత
+ చిరునామా ఇప్పటికే ఉంది
+
+
+ పేజీలో వెతకండి
+
+
+ పేజీలో వెతకండి
+
+
+ %1$d/%2$d
+
+
+ మొత్తం %2$dలో %1$d
+
+
+ తదుపరి ఫలితాన్ని కనుగొనండి
+
+ మునుపటి ఫలితాన్ని కనుగొనండి
+
+
+
+
+ డెస్క్టాప్ సైటును అభ్యర్థించు
+
+
+ డెస్క్టాప్ సైటు
+
+
+ URL కాపీ అయ్యింది
+
+
+ డెవలపర్ల పనిముట్లు
+
+
+ లంకెలను అనువర్తనాలలో తెరువు
+
+
+ ఉన్నతం
+
+
+ సైటు అనుమతులు
+
+
+ కేమెరా
+
+
+ మైక్రోఫోను
+
+
+ స్థానం
+
+
+ DRM-నియంత్రిత విషయం
+
+
+ అనుమతిని అడుగు
+
+
+ అధ్యయనాలు
+
+
+ ఇంకా తెలుసుకోండి
+
+
+ తీసివేయి
+
+
+ USB/Wi-Fi ద్వారా రిమోట్ డీబగ్గింగ్
+
+
+ వేలిముద్ర చిహ్నం
+
+
+ వేలిముద్ర గుర్తించబడలేదు. మళ్ళీ ప్రయత్నించండి.
+
+
+ వేలు చాలా వేగంగా కదిలింది. మళ్ళీ ప్రయత్నించండి.
+
+
+ వెతుకుడు సలహాలను చూపించాలా?
+
+
+ వద్దు
+
+
+ అవును
+
+
+ కొన్ని సెర్చింజన్లు సలహాలను చూపించలేవు
+
+
+ మూసివేయి
+
+
+
+
+ కొత్త ట్యాబు తెరుచుకుంది
+
+
+ మారు
+
+
+ లంకె కొత్త ట్యాబులో తెరుచుకున్న తక్షణమే దానికి మారు
+
+
+ ప్రమాదకరమైన, మోసపూరిత సైట్లను నిరోధించు
+
+
+ HTTPS-మాత్రమే రీతి
+
+
+ మినహాయింపులు
+
+ మీరు ఈ వెబ్సైట్లలో విషయ నిరోధాన్ని అచేతనం చేసారు.
+
+ తొలగించు
+
+ అన్ని వెబ్సైట్లను తీసివేయి
+
+
+ కుకీలను నిరోధించు
+
+
+ మీరు కుకీలను నిరోధించాలనుకుంటున్నారా?
+
+
+ ట్యాబు క్రాష్ అయ్యింది
+
+ క్షమించండి. ఈ ట్యాబులో ఏదో సమస్య ఉంది.
+
+ ఒక అంతరంగిక విహారిణిగా, మేము ఈ ట్యాబును భద్రపరచలేదు, దాన్ని పునరుద్ధరించలేము.
+
+ ట్యాబును మూసివేయి
+
+
+ క్రాష్ నివేదికను మొజిల్లాకు పంపు
+
+
+
+
+ %s నుండి నిరోధించిన ట్రాకర్లు
+
+ సామాజికం
+
+ అనుసంధానం సురక్షితం
+
+ నిరోధించాల్సిన ట్రాకర్లు, స్క్రిప్టులు
+
+
+ వెనుకకు వెళ్ళు
+
+
+
+ తీసివేయి
+
+
+ పేరుమార్చు
+
+ పేరుమార్చు
+
+
+
+ అలంకారం
+
+ వెలుతురు
+
+ చీకటి
+
+
+
+ ట్యాబును మూసివేయి
+
+
+ విస్మరించు
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-tg/strings.xml b/mobile/android/focus-android/app/src/main/res/values-tg/strings.xml
new file mode 100644
index 0000000000..9944640060
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-tg/strings.xml
@@ -0,0 +1,1086 @@
+
+
+
+
+
+
+
+
+ Бекор кардан
+
+ ХУБ
+
+ Нигоҳ доштан
+
+
+ Нишониеро ҷустуҷӯ кунед ё ворид намоед
+
+ Тамошокунии хусусии худкор.\nТамошо кунед. Пок созед. Такрор намоед.
+
+
+ Таърихи тамошои шумо пок карда шуд.
+ Таърихи тамошо пок карда шуд
+
+
+ Таърихи тамошо дар варақа пок карда шуд.
+
+
+ Ҷустуҷӯи «%1$s»
+
+
+ Мубодила…
+
+
+ Гузориш дар бораи мушкилии сомона
+
+
+ Кушодан дар «%1$s»
+
+
+ Кушодан дар…
+
+
+ Илова кардан ба экрани асосӣ
+
+
+ Илова кардан ба миёнбурҳо
+
+ Тоза кардан аз миёнбурҳо
+
+
+ Танзимот
+ Дар бораи барнома
+ Кумак
+ Ҳуқуқҳои шумо
+
+
+ Васоити пайгирии манъшуда
+
+
+ Хомӯш кардани ин васила метавонад баъзе мушкилиҳои сомонаро ҳал кунад
+
+
+ Манъкунии муҳтаво
+
+ Барои ҳал кардани баъзе мушкилиҳои сомона ин василаро хомӯш кунед
+
+
+ Дар %1$s асос меёбад
+
+
+ Мубодила тавассути
+
+ Таърихи тамошоро пок мекунед?
+ Барои ба таври бехатар пок кардани таърихи тамошо, лутфан, ба ин огоҳнома зер кунед ё онро тоза намоед.
+
+
+ Барои ба таври бехатар пок кардани таърихи тамошо, лутфан, ба ин огоҳнома зер кунед ё онро бо ангуш молед.
+
+ Пок кардани таърихи тамошо
+
+
+ Кушодан
+
+
+
+ Пок кардан ва кушодан
+
+
+ Пок кардан
+
+
+ Пок кардани таърихи тамошо
+
+
+
+ Пок кардан ва кушодан
+
+
+ Пок кардан ва кушодани «%1$s»
+
+
+ Ҷустуҷӯ дар «Focus»
+
+ Ҷустуҷӯ дар «Klar»
+
+ Ҷустуҷӯ дар «Focus Beta»
+
+ Ҷустуҷӯ дар «Focus Nightly»
+
+
+ «%1$s» ба шумо идора медиҳад.
+Онро ҳамчун браузери хусусӣ истифода баред:
+
+ Ҷустуҷӯ кунед ва рост дар дохили барнома тамошо намоед
+ Васоити пайгириро манъ кунед (ё танзимотро иваз карда, ба васоити пайгирӣ иҷозат диҳед)
+ Кукиҳоро нест кунед, аз он ҷумла таърихи ҷустуҷӯ ва тамошоро пок намоед
+
+
+«%1$s» аз ҷониши «Mozilla» истеҳсол карда шудааст. Мақсади мо дар мусоидат ба Интернети солим ва кушод мебошад.
+Маълумоти бештар
]]>
+
+
+ Махфият ва амният
+
+
+ Пайгирӣ, кукиҳо, интихоби маълумот
+
+
+ Танзими пешфарз, пуркунии худкор
+
+
+
+
+ Дар бораи «%1$s», кумак
+
+
+ Муҳофизати такмилёфта аз пайгирӣ
+
+
+ Муҳтавои сомона
+
+
+ Мубодилаи барномаҳо
+
+
+ Умумӣ
+
+
+ Браузери пешфарз, забон
+
+
+ Ҷамъоварӣ ва истифодабарии маълумот
+
+ Ҷустуҷӯ
+
+
+ Гирифтани пешниҳоди ҷустуҷӯ
+
+
+ «%1$s» он чизеро, ки шумо дар навори нишонӣ менависед, ба низоми ҷустуҷӯии шумо мефиристад
+
+
+ Пешфарз
+
+
+ Низоми ҷустуҷӯӣ
+
+
+ Фаъол
+
+
+ Ғайрифаъол
+
+
+ Нишонии URL-и пуркунии худкор
+
+
+ Барои сомонаҳои воло
+
+
+ Фаъол созед, то ки «%s» тавонад зиёда аз 450 нишонии URL-и роиҷро дар навори нишонӣ ба таври худкор пур кунад.
+
+
+ Барои сомонаҳое, ки шумо илова мекунед
+
+
+ Фаъол созед, то ки «%s» тавонад нишониҳои URL-и дилхоҳи шуморо ба таври худкор пур кунад.
+
+
+ Идоракунии сомонаҳо
+
+
+ Идоракунии сомонаҳо
+
+
+ + Илова кардани нишонии URL-и фармоишӣ
+
+
+ Рӯйхати шумо барои пуркунии худкор
+
+
+ Илова кардани нишонии URL
+
+
+ Илова кардани нишонии URL-и фармоишӣ
+
+
+ Илова кардани нишонии URL-и фармоишӣ
+
+
+ Илова кардани пайванд барои пуркунии худкор
+
+
+ Кукиҳо ва иттилооти сомона
+
+
+ Интихоби маълумот
+
+
+ Тоза кардани нишониҳои URL-и фармоишӣ
+
+
+ Маълумоти бештар
+
+
+ Нишониҳои URL-и фармоиширо барои пуркунии худкор илова ва идора намоед.
+
+
+ Нишонии URL, ки илова мешавад
+
+
+ Нишонии URL-ро гузоред ё ворид намоед
+
+
+ Мисол: mozilla.org
+
+
+ Мисол: example.com
+
+
+ Нишонии URL-и фармоишии нав илова карда шуд.
+
+
+ Тоза кардан
+
+
+ Тоза кардан
+
+
+ Нишонии URL-ро, ки шумо ворид кардед, такроран санҷед.
+
+ Забон
+
+
+ Забони низоми асосӣ
+
+ Махфият
+
+ Манъ кардани васоити пайгирии рекламавӣ
+ Баъзе реклама ва таблиғот боздидҳои сомонаҳои шуморо пайгирӣ мекунад, ҳатто агар шумо ба он реклама ва таблиғот зер накунед
+ Манъ кардани васоити пайгирии таҳлилӣ
+ Барои ҷамъоварӣ, таҳлил ва чен кардани фаъолиятҳои монанд ба зеркунӣ ва ҳаракаткунӣ дар экран истифода мешавад
+ Манъ кардани васоити пайгирии иҷтимоӣ
+ Ба сомонаҳо барои пайгирӣ кардани боздидҳои шумо ва намоиш додани вазифаҳои монанд ба тугмаҳои «Мубодила» дарунсохт карда мешаванд
+ Манъ кардани васоити пайгирии муҳтавои дигар
+ Фаъолсозии ин вазифа метавонад дар баъзе саҳифаҳо рафтори тасодуфию пешбининопазир ба вуҷуд орад
+ Манъ кардани кукиҳо
+
+
+ Не, ташаккур
+
+ Манъ кардани танҳо кукиҳои васоити пайгирии тарафҳои сеюм
+ Манъ кардани танҳо кукиҳои тарафҳои сеюм
+ Манъ кардани кукиҳои байнисомонавӣ
+ Ҳа, қабул
+
+
+ Истифодаи нақши ангуштон барои кушодани барнома
+
+
+ Кушодани барнома бо истифодаи нақши ангуштон, агар шумо Миёнбурҳоро илова карда бошед ё вақте ки сомона аллакай дар «%s» кушода бошад.
+
+
+ Махфӣ
+
+ Пинҳон кардани саҳифаҳои сомона ҳангоми ивазкунии барномаҳо ва манъ кардани гирифтани акси экран.
+
+ Амният
+
+ Самаранокӣ
+
+ Манъ кардани ҳуруфи интернетӣ
+
+
+ Фаъолсозии ин вазифа метавонад боиси гумшавии нишонаҳо ва тасвирҳо гардад
+
+ Манъ кардани JavaScript
+
+
+ Саҳифаҳо метавонанд тезтар кушода шаванд, аммо, инчунин, онҳо метавонанд бо рафтори тасодуфию пешбининопазир кор кунанд
+
+
+ Гузоштани «%1$s» ҳамчун браузери пешфарз
+
+ Mozilla
+ Фиристодани маълумоти истифодабарӣ
+
+
+ Маълумоти бештар
+
+
+ «Mozilla» cаъю кӯшиш мекунад, ки танҳо он маълумотеро ҷамъоварӣ намояд, ки мо бояд барои фаъолият ва беҳтарсозии «%1$s» барои ҳар як кас таъмин намоем.
+
+
+ Огоҳномаи махфият
+
+
+ Маълумот дар бораи иҷозатнома
+
+
+ Китобхонаҳое, ки мо истифода мебарем
+
+
+ %s | Китобхонаҳои OSS
+
+
+ Дар бораи «%1$s»
+
+
+ Низомҳои ҷустуҷӯии насбшуда
+
+
+ Интихоби низоми ҷустуҷӯӣ
+
+
+ Барқарор кардани низоми ҷустуҷӯии пешфарз
+
+
+ + Илова кардани низоми ҷустуҷӯии дигар
+ Тоза кардани низомҳои ҷустуҷӯӣ
+ Тоза кардан
+
+
+ Илова кардани низоми ҷустуҷӯии дигар
+
+ Низоми ҷустуҷӯии дилхоҳи худро интихоб намоед:
+
+
+ Илова кардани низоми ҷустуҷӯӣ
+
+ Номи низоми ҷустуҷӯӣ
+ Сатри ҷустуҷӯ барои истифода
+ Нигоҳ доштан
+
+
+ Мисол: example.com/search/?q=%s
+
+ Низоми ҷустуҷӯии нав илова шуд.
+
+ Номи низоми ҷустуҷӯиро ворид намоед
+
+ Низоми ҷустуҷӯии насбшуда аллакай ин номро истифода мебарад.
+
+ Сатри ҷустуҷӯро ворид кунед
+
+ Тафтиш кунед, ки сатри ҷустуҷӯ ба шакли намунавӣ мувофиқат мекунад
+
+
+ Пок кардани матн
+
+
+ Нодида гузарондан
+
+
+ Пок кардани таърихи тамошо
+
+
+ Шумораи варақаҳои кушода: %1$s
+
+
+ Пайвасти боэътимод
+
+
+ Бор шуда истодааст
+
+
+ Сомона бор карда шуд
+
+
+ Имконоти бештар
+
+
+ Тугмаи имконоти бештар
+
+
+ Ба пеш
+
+
+ Аз нав бор кардани сомона
+
+
+ Ба қафо гузаштан
+
+
+ Қатъ кардани боркунии сомона
+
+
+ Бозгашт ба барномаи қаблӣ
+
+
+ Шумораи васоити пайгирии манъшуда
+
+
+ Манъ кардани васоити пайгирӣ
+
+ Ҳуқуқҳои шумо
+
+ Кушодани пайванд дар барномаи дигар
+
+
+ Шумо метавонед аз «%1$s» бароед, то ки ин пайванд дар «%2$s» кушода шавад.
+
+ Барномаеро ёбед, ки метавонад ин пайвандро кушояд
+
+ Ҳеҷ яке аз барномаҳои дастгоҳи шумо ин пайвандро кушода наметавонад. Шумо метавонед аз «%1$s» бароед ва барномаи мувофиқро тавассути «%2$s» ҷустуҷӯ кунед.
+
+ Аз тамошокунии махфӣ мебароед?
+
+
+ «%1$s» анҷом ёфт
+
+
+ Кушодан
+
+
+ Ба миёнбурҳо илова карда шуд!
+
+ Сервер ёфт нашуд
+
+
+ Пӯшидан
+
+
+
+ Хуш омадед ба «%1$s»
+
+
+ Тезкор. Хусусӣ. Бе саргармии бегона.
+
+
+ Оғози кор
+
+
+
+ Дар ҷаҳон мисли «%1$s» ягон браузери дигар нест
+
+
+ Барои баланд бардоштани сатҳи махфият, вақте ки шумо барномаро мепӯшед, мо таърихи тамошои шуморо тоза мекунем.
+
+
+
+ Барои муҳофизат кардани маълумоти худ, ҳангоми кушодани ҳар як пайванд, аз браузери «%1$s» ба сурати пешфарз истифода баред.
+
+
+ Гузоштан ҳамчун браузери пешфарз
+
+
+ Нодида гузарондан
+
+
+ Махфияти худро тақвият диҳед
+
+
+ Тамошокунии хусусиро ба сатҳи нав баланд бардоред. Таблиғоту реклама ва муҳтавои дигареро, ки метавонад шуморо байни сомонаҳо пайгирӣ кунад ва вақти кушодани саҳифаҳоро хеле суст намоянд, манъ кунед.
+
+
+ Ҷустуҷӯи шумо, Интернети шумо
+
+
+ Дар ҷустуҷӯи чизи дигар қарор доред? Низоми ҷустуҷӯии дигареро дар Танзимот интихоб карда, ба таври пешфарз истифода баред.
+
+
+ Миёнбурҳоро ба экрани асосӣ илова намоед
+
+
+ Ба воситаи «%1$s» ба сомонаҳои дӯстдоштаи худ тез баргардед. Танҳо аз менюи «%1$s» имкони «Илова кардан ба экрани асосӣ»-ро интихоб намоед.
+
+
+ Махфиятро ба одати худ табдил диҳед
+
+
+ «%1$s»-ро ҳамчун браузери пешфарзи худ танзим карда, истифода баред ва ҳамаи афзалиятҳои тамошокунии хусусиро ҳангоми кушодани саҳифаҳои сомонаҳо аз барномаҳои дигар ба даст оред.
+
+ Хуб, фаҳмидам!
+ Нодида гузарондан
+ Навбатӣ
+
+
+ -
+
+
+ Илова кардан
+
+ ҲА
+
+
+ Бекор кардан
+
+ НЕ
+
+
+ Миёнбур бе фаъолсозии «Муҳофизати такмилёфта аз пайгирӣ» кушода мешавад
+
+
+ Ҷаласаи тамошокунии хусусӣ
+
+
+ Огоҳномаҳо ба шумо имкон медиҳанд, то тавонед ҷаласаи худро дар «%1$s» бо як ламс тоза намоед. Ба шумо лозим нест, ки браузери худро кушоед ва бинед, ки дар он чӣ иҷро шуда истодааст.
+
+
+ Пок кардани таърихи тамошо
+
+
+ Боргирӣ кардани «Firefox»
+
+
+
+
+
+ Иҷозатномаи ҷамъиятии «Mozilla» ва иҷозатномаҳои манбаи кушоди дигар ба шумо барои истифода дастрас шудааст.]]>
+
+
+ дар ин ҷо пайдо карда шавад.]]>
+
+
+ иҷозатномаҳои гуногуни дигари манбаи кушод ва ройгон дастрас аст.]]>
+
+
+ Иҷозатномаи ҷамъиятии умумии GNU v3 истифода мебарад ва рӯйхати мазкур дар ин ҷо дастрас аст.]]>
+
+
+ Номи корбар
+ Ниҳонвожа
+ Пок кардан
+
+
+
+ Пайвасти боэътимод
+ Пайвасти беэътимод
+
+ Тасдиқ аз ҷониби: %1$s
+
+
+ Амнияти сомона
+ Нишонии URL аллакай вуҷуд дорад
+
+
+ Ҷустуҷӯ дар саҳифа
+
+
+ Ҷустуҷӯ дар саҳифа
+
+
+ %1$d/%2$d
+
+ %1$d аз %2$d
+
+
+ Ҷустуҷӯи натиҷаи навбатӣ
+
+ Ҷустуҷӯи натиҷаи қаблӣ
+
+ Қатъ кардани ҷустуҷӯ дар саҳифа
+
+
+ Дархост кардани версияи пурраи сомона
+
+
+ Нусха барои компютери мизи корӣ
+
+
+ Нишонии URL нусха бардошта шуд
+
+
+ Абзорҳои барномасозӣ
+
+
+ Кушодани пайвандҳо дар барномаҳо
+
+
+ Иловагӣ
+
+
+ Иҷозатҳои сомона
+
+
+ Маҳдудкунии баннери куки
+
+
+ Фаъол
+
+
+ Ғайрифаъол
+
+
+ Маҳдудкунии баннери куки
+
+
+ Ба қадри имкон, дархостҳои кукиҳоро ба таври худкор рад кунед, то ки шумораи намоиши баннерҳо кам карда шавад.
+
+ -->
+ Маҳдудкунии баннери куки
+
+
+ Барои ин сомона фаъол аст
+
+
+ Ин сомона дар айни замон дастгирӣ намешавад
+
+
+ Барои ин сомона ғайрифаъол аст
+
+
+ Маҳдудкунии баннери куки
+
+
+ Барои ин сомона ғайрифаъол аст
+
+
+ Барои ин сомона фаъол аст
+
+
+ «Маҳдудкунии баннери куки»-ро барои %1$s фаъол месозед?
+
+
+ «Маҳдудкунии баннери куки»-ро барои %1$s хомӯш месозед?
+
+
+ %1$s кукиҳои ин сомонаро тоза мекунад ва саҳифаро аз нав бор мекунад. Амали тозакунии ҳамаи кукиҳо метавонад шуморо аз сомона хориҷ кунад ва сабадҳои харидории шуморо холӣ намояд.
+
+
+ «%1$s» метавонад кӯшиш кунад, ки дархостҳои кукиҳоро ба таври худкор рад кунад.
+
+
+ Айни ҳол ин сомона аз тарафи «Маҳдудкунии баннери куки» дастгирӣ намешавад. Шумо мехоҳед, ки аз дастаи мо дархост кунед, ки он ин сомонаро бозбинӣ кунад ва онро барои дастгирӣ дар оянда илова намояд?
+
+
+ Бекор кардан
+
+
+ Дархост кардани дастгирӣ
+
+
+ Дархост барои дастгирӣ кардани сомонаи пешниҳодшуда.
+
+
+ Дархост барои дастгирӣ кардани сомонаи пешниҳодшуда.
+
+
+
+ Барои қатъ кардани намоиши баннерҳои кукии безоркунанда, «%1$s» кӯшиш мекунад, ки дархостҳои кукиҳоро рад намояд.\n\nХусусиятҳои баннери кукиҳоро дар «%2$s» идора кунед.
+
+ танзимот
+
+
+ Пахши худкор
+
+
+ Барои иҷозат додан:
+
+
+ 1. Ба Танзимоти Android гузаред
+
+
+ Иҷозатҳо-ро зер кунед]]>
+
+
+ Ба Танзимот гузаред
+
+
+ %1$s-ро ба ФАЪОЛ иваз намоед]]>
+
+
+ Камера
+
+
+ Микрофон
+
+
+ Ҷойгиршавӣ
+
+
+ Огоҳнома
+
+
+ Муҳтавои идорашавандаи DRM
+
+
+ Дархости иҷозат
+
+
+ Манъ карда мешавад
+
+
+ Иҷозат дода мешавад
+
+
+ Аз тарафи «Android» манъ карда шуд
+
+
+ Иҷозат додани аудио ва видео
+
+
+ Бастани танҳои аудио
+
+
+ Тавсияшуда
+
+
+ Бастани аудио ва видео
+
+
+ Омӯзишҳо
+
+
+ Баъзе вақт «Firefox» метавонад омӯзишҳоро насб ва иҷро намояд.
+
+
+ Маълумоти бештар
+
+
+ Барои татбиқ кардани тағйирот барнома пӯшида мешавад
+
+
+ Тоза кардан
+
+
+ Фаъол
+
+
+ Ба анҷом расид
+
+
+ Ислоҳкунии дурдасти хатоҳо тавассути SB/Wi-Fi
+
+
+ Кушодани қулф
+
+
+ Истифодаи нақши ангушти худро тасдиқ намоед
+
+
+ Барои идома додани кор бо ҷаласаи ҷории барнома, шумо метавонед аз нақши ангушти худ истифода баред.
+
+
+ Кушодани пайванд дар ҷаласаи нав
+
+
+ Нишони нақши ангуштон
+
+
+ Нақши ангушт шинохта нашуд. Аз нав кӯшиш кунед.
+
+
+ Ангушт хеле тез ҳаракат кард. Аз нав кӯшиш кунед.
+
+
+ Пешниҳодҳои ҷустуҷӯ нишон дода шаванд?
+
+
+ Барои намоиш додани пешниҳодҳои ҷустуҷӯ, «%1$s» бояд он чизеро, ки шумо дар навори нишонӣ менависед ба низоми ҷустуҷӯӣ ирсол кунад.
+
+
+ Не
+
+
+ Ҳа
+
+
+ Баъзе низомҳои ҷустуҷӯӣ пешниҳодҳоро нишон дода наметавонанд.
+
+
+ Нодида гузарондан
+
+
+
+
+ Сомона бо рафтори тасодуфию пешбининопазир кор мекунад?\n
+ Имкони «Муҳофизат аз пайгирӣ»-ро хомӯш кунед
+
+
+ Илова кардан ба экрани асосӣ]]>
+
+
+ Ҳар як пайвандро дар «%1$s» кушоед\n
+ Браузери «%1$s»-ро ҳамчун барномаи пешфарз танзим карда, истифода баред
+
+
+
+ Пуркунии худкори нишонаҳои URL барои сомонаҳои писандида\n
+ Нишонии URL-и дилхоҳро ба муддати дароз дар навори нишонӣ пахш кунед
+
+
+
+ Пайвандеро дар варақаи нав кушоед\n
+ Ҳар як пайванди саҳифаро ба муддати дароз пахш намоед
+
+
+
+ Хомӯш кардани маслиҳатҳо дар экрани оғози кор
+
+
+ Варақаи нав кушода шуд
+
+
+ Қатъу васл кардан
+
+
+ Ба реҷаи экрани пурра ворид шуда истодааст
+
+
+ Гузариши фаврӣ ба пайванди кушодашуда дар варақаи нав
+
+
+ Манъ кардани муҳтавои зараровар ва сомонаҳои қалбакии имконпазир
+
+ Манъ кардани сомонаҳои фиребанда ва ҳамлакунанда, сомонаҳои зараровар ва сомонаҳо бо нармафзори беҳуда.
+
+ Реҷаи «Танҳо HTTPS»
+
+ Ба таври худкор кӯшиш мекунад, ки ба сомонаҳо бо истифода аз протоколи рамзгузории HTTPS барои баланд бардоштани амният пайваст шавад.
+
+
+ Истисноҳо
+
+ Шумо имкони манъкунии муҳтаво барои сомонаҳои мазкур ғайрифаъол кардед.
+
+ Тоза кардан
+
+ Тоза кардани ҳамаи сомонаҳо
+
+
+ Манъ кардани кукиҳо
+
+
+ Шумо мехоҳед, ки кукиҳоро манъ кунед?
+
+
+ Варақа вайрон шуд
+
+ Бубахшед. Барои ин варақа мушкилӣ ба миён омад.
+
+ Ҳамчун браузери хусусӣ, мо ҳеҷ вақт маълумоти варақаро нигоҳ намедорем ва онро барқарор карда наметавонем.
+
+ Пӯшидани варақа
+
+
+ Фиристодани гузориш дар бораи вайронӣ ба Mozilla
+
+
+
+
+ Шумораи васоити пайгирии манъшуда аз санаи %s
+
+ Муҳтаво
+
+
+ Реклама
+
+ Иҷтимоӣ
+
+ Таҳлил
+
+ Муҳофизати такмилёфта аз пайгирӣ
+
+ Муҳофизат барои ин сомона ғайрифаъол аст
+
+ Муҳофизат барои ин сомона фаъол аст
+
+ Пайвастшавӣ бехатар аст
+
+ Пайвастшавӣ бехатар нест
+
+
+ Васоити пайгирӣ ва скриптҳое, ки манъ карда мешаванд
+
+
+ Бозгашт
+
+
+
+ Тоза кардан
+
+ Иваз кардани ном
+
+ Иваз кардани ном
+
+ Номи миёнбур
+
+
+ Вақте ки шумо таърихи «%1$s»-ро тоза мекунед, тасвирҳои нигоҳдошташуда ва мубодилашуда <b>нест карда намешаванд</b>
+
+
+
+ Мавзуъ
+
+ Равшан
+
+ Торик
+
+ Аз ҷониби сарфаи батарея муқаррар карда шудааст
+
+ Дар мавзуи дастгоҳ асос меёбад
+
+
+ Ин сомона пайвасти «HTTPS»-ро дастгирӣ намекунад
+
+
+ Маълумоти бештар
+ Шумо метавонед ин танзимро дар Танзимот > Махфият ва амният > Бехатарӣ иваз кунед.]]>
+
+
+ Пайвастшавӣ бехатар нест
+
+
+
+ Агар шумо қаблан ба ин сервер бо муваффақият пайваст шуда бошед, эҳтимол ин хато муваққатӣ аст ва шумо метавонед баъдтар амалро аз нав кӯшиш намоед.
+ ]]>
+
+
+ Касе метавонад тақаллуб кардани сомонаро кӯшиш намояд ва идомаи кор бо ин сомона хатарнок аст.
+
+ «%1$s» ба %2$s эътимод надорад, зеро барорандаи гувоҳнома номаълум аст, гувоҳнома ба таври худ имзо гузоштааст ё сервер гувоҳномаҳои фосилавии дурустро намефиристад.
+ ]]>
+
+
+
+ Пӯшидани варақа
+
+
+
+ Қапидем! Мо ин сомонаро манъ кардем, то ки аз қафои шумо ҷосусӣ накунад. Барои дидани он чизеро, ки мо манъ карда истодаем, дар вақти дилхоҳ ба сипар зер кунед.
+
+
+ Пӯшидани равзанаҳои зоҳиршаванда
+
+
+
+ Мо шуморо муҳофизат мекунем!
+
+ Ҳамин танзимоти пешфарз муҳофизати пурқувватро пешниҳод мекунанд. Аммо шумо метавонед танзимоти пешниҳодшударо мувофиқи ниёзҳои хусусии худ ба осони иваз намоед.
+
+ Нодида гузарондан
+
+
+ Дар ин ҷо зер кунед, то ки ҳамааш нест карда шавад — таърих, кукиҳо, ҳама чиз — ва пас бо варақаи нав тамошои тозаи худро оғоз намоед.
+
+
+
+
+ Пӯшидан
+
+
+ Виҷети ҷустуҷӯ
+
+
+ Таърихи тамошо пок карда шуд! 🎉
+
+
+ Ҷаласаи тамошокунии хусусиро оғоз намоед ва мо васоити пайгирӣ ва дигар чизҳои зарароварро аз роҳи шумо тоза мекунем.
+
+
+ Мо шуморо ба тамошокунии хусусӣ ворид мекунем, аммо дар оянда браузери худро ба воситаи виҷети «%1$s» аз экрани асосии худ зудтар оғоз намоед.
+
+
+ Илова кардани виҷет ба экрани асосӣ
+
+
+ Виҷет ба экрани асосӣ илова карда шуд
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-th/strings.xml b/mobile/android/focus-android/app/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000000..f2272faa3e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-th/strings.xml
@@ -0,0 +1,1119 @@
+
+
+
+
+
+
+
+
+ ยกเลิก
+
+ ตกลง
+
+ บันทึก
+
+
+ ค้นหาหรือป้อนที่อยู่
+
+ ท่องเว็บแบบส่วนตัวโดยอัตโนมัติ\nท่องเว็บ ล้าง ทำซ้ำ
+
+
+ ล้างประวัติการท่องเว็บของคุณแล้ว
+ ล้างประวัติการท่องเว็บแล้ว
+
+
+ ล้างประวัติการท่องเว็บของแท็บแล้ว
+
+
+ ค้นหา %1$s
+
+
+ แบ่งปัน…
+
+
+ รายงานปัญหาไซต์
+
+
+ เปิดใน %1$s
+
+
+ เปิดใน…
+
+
+ เพิ่มไปยังหน้าจอหลัก
+
+
+ เพิ่มทางลัด
+
+ ลบออกจากคำสั่งลัด
+
+
+ การตั้งค่า
+ เกี่ยวกับ
+ ช่วยเหลือ
+ สิทธิของคุณ
+
+
+ ตัวติดตามที่ถูกปิดกั้น
+
+
+ การปิดสิ่งนี้อาจแก้ปัญหาไซต์บางอย่าง
+
+
+ การปิดกั้นเนื้อหา
+
+ ปิดเพื่อแก้ไขไซต์บางส่วน
+
+
+ ขับเคลื่อนโดย %1$s
+
+
+ แบ่งปันผ่าน
+
+ ล้างประวัติการท่องเว็บ?
+ แตะหรือล้างการแจ้งเตือนนี้เพื่อลบประวัติการท่องเว็บของคุณอย่างปลอดภัย
+
+
+ แตะหรือปัดการแจ้งเตือนนี้เพื่อลบประวัติการเรียกดูของคุณอย่างปลอดภัย
+
+ ล้างประวัติการท่องเว็บ
+
+
+ เปิด
+
+
+ ล้างแล้วเปิด
+
+
+ ล้าง
+
+
+ ล้างประวัติการท่องเว็บ
+
+
+
+ ล้างแล้วเปิด
+
+
+ ล้างแล้วเปิด %1$s
+
+
+
+ ค้นหาใน Focus
+
+ ค้นหาใน Klar
+
+ ค้นหาใน Focus Beta
+
+ ค้นหาใน Focus Nightly
+
+
+ %1$s ให้คุณควบคุมได้ทุกอย่าง
+ใช้เป็นเบราว์เซอร์ส่วนตัว:
+
+ ค้นหาและเรียกดูได้จากในแอปโดยตรง
+ ปิดกั้นตัวติดตาม (หรืออัปเดตการตั้งค่าเพื่ออนุญาตตัวติดตาม)
+ ล้างเพื่อลบคุกกี้รวมทั้งประวัติการค้นหาและการเรียกดู
+
+
+%1$s ถูกสร้างขึ้นโดย Mozilla ภารกิจของเราคือส่งเสริมอินเทอร์เน็ตที่สมบูรณ์และเปิดกว้าง
+เรียนรู้เพิ่มเติม
]]>
+
+
+ ความเป็นส่วนตัวและความปลอดภัย
+
+
+ การติดตาม, คุกกี้, ทางเลือกข้อมูล
+
+
+ ตั้งค่าเริ่มต้น, การเติมอัตโนมัติ
+
+
+
+
+ เกี่ยวกับ %1$s, ช่วยเหลือ
+
+
+ การป้องกันการติดตามแบบพิเศษ
+
+
+ เนื้อหาเว็บ
+
+
+ การสลับแอป
+
+
+ ทั่วไป
+
+
+ เบราว์เซอร์เริ่มต้น, ภาษา
+
+
+ การเก็บรวบรวมและใช้ข้อมูล
+
+ ค้นหา
+
+
+ รับข้อเสนอแนะการค้นหา
+
+ %1$s จะส่งสิ่งที่คุณพิมพ์ในแถบที่อยู่ไปยังเครื่องมือค้นหาของคุณ
+
+
+ ค่าเริ่มต้น
+
+
+ เครื่องมือค้นหา
+
+
+ เปิด
+
+
+ ปิด
+
+
+ การเติม URL อัตโนมัติ
+
+
+ สำหรับไซต์เด่น
+
+
+ เปิดใช้งานเพื่อให้ %s เติมมากกว่า 450 URL ยอดนิยมโดยอัตโนมัติในแถบที่อยู่
+
+
+ สำหรับไซต์ที่คุณเพิ่ม
+
+
+ เปิดใช้งานเพื่อให้ %s เติม URL โปรดของคุณโดยอัตโนมัติ
+
+
+ จัดการไซต์
+
+
+ จัดการไซต์
+
+
+ + เพิ่ม URL ที่กำหนดเอง
+
+
+ รายการเติมอัตโนมัติของคุณ:
+
+
+ เพิ่ม URL
+
+
+ เพิ่ม URL ที่กำหนดเอง
+
+
+ เพิ่ม URL ที่กำหนดเอง
+
+
+ เพิ่มลิงก์ไปยังการเติมอัตโนมัติ
+
+
+ คุกกี้และข้อมูลไซต์
+
+
+ ทางเลือกข้อมูล
+
+
+ เอา URL ที่กำหนดเองออก
+
+
+ เรียนรู้เพิ่มเติม
+
+
+ เพิ่มและจัดการ URL เติมอัตโนมัติที่กำหนดเอง
+
+
+ URL ที่จะเพิ่ม
+
+
+ วางหรือป้อน URL
+
+
+ ตัวอย่าง: mozilla.org
+
+
+ ตัวอย่าง: example.com
+
+
+ เพิ่ม URL ที่กำหนดเองใหม่แล้ว
+
+
+ เอาออก
+
+
+ เอาออก
+
+
+ ตรวจสอบ URL ที่คุณป้อนอีกครั้ง
+
+ ภาษา
+
+ ค่าเริ่มต้นของระบบ
+
+ ความเป็นส่วนตัว
+ ปิดกั้นตัวติดตามของโฆษณา
+ โฆษณาบางตัวติดตามการเยี่ยมชมไซต์ แม้ว่าคุณไม่ได้คลิกโฆษณา
+ ปิดกั้นตัวติดตามของการวิเคราะห์
+ ใช้เพื่อเก็บรวบรวม วิเคราะห์ และวัดกิจกรรมอย่างการแตะและการเลื่อน
+ ปิดกั้นตัวติดตามทางสังคม
+ ฝังอยู่ในไซต์เพื่อติดตามการเยี่ยมชมของคุณและแสดงผลฟังก์ชันการทำงานอย่างปุ่มแบ่งปัน
+ ปิดกั้นตัวติดตามของเนื้อหาอื่น ๆ
+ การเปิดใช้งานอาจส่งผลให้หน้าบางส่วนทำงานอย่างไม่คาดคิด
+ ปิดกั้นคุกกี้
+
+
+ ไม่ ขอบคุณ
+ ปิดกั้นคุกกี้ของตัวติดตามจากบุคคลที่สามเท่านั้น
+ ปิดกั้นคุกกี้จากบุคคลที่สามเท่านั้น
+
+ ปิดกั้นคุกกี้ข้ามไซต์
+ ใช่ ได้โปรด
+
+
+ ใช้ลายนิ้วมือเพื่อปลดล็อคแอป
+
+
+ ปลดล็อกโดยใช้ลายนิ้วมือหากคุณได้เพิ่มทางลัดหรือเมื่อเว็บไซต์เปิดอยู่ใน %s แล้ว
+
+
+ การพรางตัว
+
+ ซ่อนหน้าเว็บเมื่อสลับแอปและปิดกั้นการจับภาพหน้าจอ
+
+ ความปลอดภัย
+
+ ประสิทธิภาพ
+ ปิดกั้นแบบอักษรเว็บ
+
+ อาจส่งผลให้ไอคอนหรือภาพขาดหายไป
+
+ ปิดกั้น JavaScript
+
+ หน้าอาจโหลดเร็วขึ้น แต่อาจทำงานอย่างไม่คาดคิด
+
+
+ ทำให้ %1$s เป็นเบราว์เซอร์เริ่มต้น
+
+ Mozilla
+ ส่งข้อมูลการใช้งาน
+
+
+ เรียนรู้เพิ่มเติม
+
+
+ Mozilla มุ่งมั่นที่จะเก็บรวบรวมเฉพาะสิ่งที่เราจำเป็นต้องให้บริการและปรับปรุง %1$s สำหรับทุกคน
+
+
+ ประกาศความเป็นส่วนตัว
+
+
+ ข้อมูลสัญญาอนุญาต
+
+
+ ไลบรารีที่เราใช้
+
+
+ %s | ไลบรารี OSS
+
+
+ เกี่ยวกับ %1$s
+
+
+ เครื่องมือค้นหาที่ติดตั้งไว้
+
+
+ เลือกเครื่องมือค้นหา
+
+
+ เรียกคืนเครื่องมือค้นหาเริ่มต้น
+
+
+ + เพิ่มเครื่องมือค้นหาอื่น
+ เอาเครื่องมือค้นหาออก
+ เอาออก
+
+
+ เพิ่มเครื่องมือค้นหาอื่น
+
+
+ เลือกเครื่องมือที่คุณต้องการ:
+
+
+ เพิ่มเครื่องมือค้นหา
+
+ ชื่อเครื่องมือค้นหา
+ สตริงการค้นหาที่จะใช้
+ บันทึก
+
+
+ ตัวอย่าง: example.com/search/?q=%s
+
+ เพิ่มเครื่องมือค้นหาใหม่แล้ว
+
+ ป้อนชื่อเครื่องมือค้นหา
+ เครื่องมือค้นหาที่ติดตั้งไว้กำลังใช้ชื่อนั้นอยู่แล้ว
+
+ ป้อนสตริงการค้นหา
+
+ ตรวจสอบว่าสตริงการค้นหาตรงกับรูปแบบตัวอย่าง
+
+
+ ล้างข้อมูลที่ป้อน
+
+
+ ยกเลิก
+
+
+ ล้างประวัติการท่องเว็บ
+
+
+ แท็บที่เปิดอยู่: %1$s
+
+
+ การเชื่อมต่อปลอดภัย
+
+
+ กำลังโหลด
+
+
+ โหลดเว็บไซต์แล้ว
+
+
+ ตัวเลือกเพิ่มเติม
+
+
+ ปุ่มตัวเลือกเพิ่มเติม
+
+
+ นำทางเดินหน้า
+
+
+ โหลดเว็บไซต์ใหม่
+
+
+ นำทางย้อนกลับ
+
+
+ หยุดการโหลดเว็บไซต์
+
+
+ กลับไปที่แอปก่อนหน้า
+
+
+ จำนวนตัวติดตามที่ถูกปิดกั้น
+
+
+ ปิดกั้นตัวติดตาม
+
+ สิทธิของคุณ
+
+ เปิดลิงก์ในแอปอื่น
+
+ คุณสามารถออกจาก %1$s เพื่อเปิดลิงก์นี้ใน %2$s
+
+ ค้นหาแอปที่สามารถเปิดลิงก์
+
+ ไม่มีแอปในอุปกรณ์ของคุณที่สามารถเปิดลิงก์นี้ คุณสามารถออกจาก %1$s เพื่อค้นหา %2$s สำหรับแอปที่สามารถทำได้
+
+ ออกจากการท่องเว็บแบบส่วนตัว?
+
+
+ %1$s เสร็จแล้ว
+
+
+ เปิด
+
+
+
+
+
+
+
+
+
+
+ เพิ่มทางลัดแล้ว!
+
+ ไม่พบเซิร์ฟเวอร์
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ปิด
+
+
+
+ ยินดีต้อนรับสู่ %1$s
+
+
+ เร็ว ส่วนตัว ไม่มีการรบกวน
+
+
+ เริ่มต้นใช้งาน
+
+
+
+ %1$s ไม่เหมือนบราวเซอร์อื่นๆ
+
+
+ เราล้างประวัติของคุณเมื่อคุณปิดแอพเพื่อความเป็นส่วนตัวกว่าปกติ
+
+
+
+ ทำให้ %1$s เป็นค่าเริ่มต้นของคุณเพื่อปกป้องข้อมูลของคุณด้วยทุกลิงก์ที่คุณเปิด
+
+
+ ตั้งเป็นเบราว์เซอร์เริ่มต้น
+
+
+ ข้าม
+
+
+
+ เพิ่มความเป็นส่วนตัวของคุณ
+
+ นำการท่องเว็บแบบส่วนตัวไปสู่ระดับถัดไป ปิดกั้นโฆษณาและเนื้อหาอื่น ๆ ที่สามารถติดตามคุณในไซต์ต่าง ๆ และลดเวลาในการโหลดหน้า
+
+
+ การค้นหาของคุณในแบบของคุณ
+
+ กำลังค้นหาอย่างอื่น? เลือกเครื่องมือค้นหาเริ่มต้นอื่นในการตั้งค่า
+
+
+ เพิ่มทางลัดไปยังหน้าจอหลักของคุณ
+
+ กลับไปที่ไซต์โปรดของคุณใน %1$s ได้อย่างรวดเร็ว เพียงเลือก \"เพิ่มไปยังหน้าจอหลัก\" จากเมนู %1$s
+
+
+ สร้างนิสัยความเป็นส่วนตัว
+
+ ตั้ง %1$s เป็นเบราว์เซอร์เริ่มต้นของคุณและรับประโยชน์จากการท่องเว็บแบบส่วนตัวเมื่อคุณเปิดหน้าเว็บจากแอปอื่น ๆ
+
+ ตกลง เข้าใจแล้ว!
+ ข้าม
+ ถัดไป
+
+
+ -
+
+
+ เพิ่ม
+
+
+ ใช่
+
+
+ ยกเลิก
+
+
+ ไม่
+
+
+ ทางลัดจะเปิดพร้อมปิดใช้งานการป้องกันการติดตามแบบพิเศษ
+
+
+ วาระการท่องเว็บแบบส่วนตัว
+
+
+ การแจ้งเตือนช่วยให้คุณล้างวาระ %1$s ของคุณด้วยการแตะ คุณไม่จำเป็นต้องเปิดแอปหรือดูสิ่งที่กำลังทำงานในเบราว์เซอร์ของคุณ
+
+
+ ล้างประวัติการท่องเว็บ
+
+
+ ดาวน์โหลด Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License และสัญญาอนุญาตการเปิดต้นฉบับอื่น ๆ]]>
+
+
+ ที่นี่]]>
+
+
+ สัญญาอนุญาต เสรีและเปิดต้นฉบับต่าง ๆ]]>
+
+
+ GNU General Public License v3 และใช้ได้ ที่นี่ ]]>
+
+
+ ชื่อผู้ใช้
+ รหัสผ่าน
+ ล้าง
+
+
+
+ การเชื่อมต่อปลอดภัย
+ การเชื่อมต่อไม่ปลอดภัย
+
+ ยืนยันโดย: %1$s
+
+
+ ความปลอดภัยของไซต์
+ มี URL อยู่แล้ว
+
+
+ ค้นหาในหน้า
+
+
+ ค้นหาในหน้า
+
+
+ %1$d/%2$d
+
+ %1$d จาก %2$d
+
+
+ ค้นหาผลลัพธ์ถัดไป
+
+ ค้นหาผลลัพธ์ก่อนหน้า
+
+ ยกเลิกการค้นหาในหน้า
+
+
+
+
+ ขอไซต์สำหรับเดสก์ท็อป
+
+
+ ไซต์เดสก์ท็อป
+
+
+ คัดลอก URL แล้ว
+
+
+ เครื่องมือนักพัฒนา
+
+
+ เปิดลิงก์ในแอป
+
+
+ ขั้นสูง
+
+
+ สิทธิอนุญาตไซต์
+
+
+ การลดแบนเนอร์คุกกี้
+
+
+ เปิด
+
+
+ ปิด
+
+
+ การลดแบนเนอร์คุกกี้
+
+
+ เห็นแบนเนอร์น้อยลงโดยปฏิเสธคำขอคุกกี้โดยอัตโนมัติเมื่อเป็นไปได้
+
+ -->
+ การลดแบนเนอร์คุกกี้
+
+
+ เปิดสำหรับไซต์นี้
+
+
+ ไม่รองรับไซต์ในขณะนี้
+
+
+ ปิดสำหรับไซต์นี้
+
+
+ การลดแบนเนอร์คุกกี้
+
+
+ ปิดสำหรับไซต์นี้
+
+
+ เปิดสำหรับไซต์นี้
+
+
+ ต้องการเปิดการลดแบนเนอร์คุกกี้สำหรับ %1$s หรือไม่?
+
+
+ ต้องการปิดการลดแบนเนอร์คุกกี้สำหรับ %1$s หรือไม่?
+
+
+ %1$s จะล้างคุกกี้ของไซต์นี้และรีเฟรชหน้า การล้างคุกกี้ทั้งหมดอาจนำคุณออกจากระบบหรือล้างรถเข็นช็อปปิ้ง
+
+
+ %1$s สามารถพยายามปฏิเสธคำขอคุกกี้โดยอัตโนมัติได้
+
+
+ ขณะนี้ไซต์นี้ไม่รองรับการลดแบนเนอร์คุกกี้ คุณต้องการขอให้ทีมของเราตรวจสอบเว็บไซต์นี้และเพิ่มการรองรับในอนาคตหรือไม่?
+
+
+ ยกเลิก
+
+
+ ขอการรองรับ
+
+
+ ส่งคำขอรองรับไซต์แล้ว
+
+
+ ส่งคำขอรองรับไซต์แล้ว
+
+
+
+ %1$s จะพยายามปฏิเสธคำขอคุกกี้เพื่อปิดแบนเนอร์คุกกี้ที่น่ารำคาญ\n\nโปรดจัดการการกำหนดลักษณะแบนเนอร์คุกกี้ใน %2$s
+
+ การตั้งค่า
+
+
+ เล่นอัตโนมัติ
+
+
+ เมื่อต้องการอนุญาต:
+
+
+ 1. ไปที่การตั้งค่า Android
+
+
+ สิทธิอนุญาต]]>
+
+
+ ไปยังการตั้งค่า
+
+
+ %1$s เป็นเปิด]]>
+
+
+ กล้อง
+
+
+ ไมโครโฟน
+
+
+ ตำแหน่งที่ตั้ง
+
+
+ การแจ้งเตือน
+
+
+ เนื้อหาที่ควบคุมด้วย DRM
+
+
+ ขออนุญาตทุกครั้ง
+
+
+ ปิดกั้น
+
+
+ อนุญาต
+
+
+ ปิดกั้นโดย Android
+
+
+ อนุญาตเสียงและวิดีโอ
+
+
+ ปิดกั้นเสียงเท่านั้น
+
+
+ แนะนำ
+
+
+ ปิดกั้นเสียงและวิดีโอ
+
+
+ การศึกษา
+
+
+ Firefox อาจติดตั้งและเรียกใช้การศึกษาเป็นครั้งคราว
+
+
+ เรียนรู้เพิ่ม
+
+
+ แอปพลิเคชันจะปิดตัวเพื่อนำการเปลี่ยนแปลงไปใช้
+
+
+ เอาออก
+
+
+ ทำงานอยู่
+
+
+ เสร็จสมบูรณ์
+
+
+ การดีบั๊กระยะไกลผ่าน USB/Wi-Fi
+
+
+ ปลดล็อก
+
+
+ ยืนยันการใช้ลายนิ้วมือของคุณ
+
+
+ คุณสามารถใช้ลายนิ้วมือเพื่อใช้งานแอปปัจจุบันต่อได้
+
+
+ เปิดลิงก์ในวาระใหม่
+
+
+ ไอคอนลายนิ้วมือ
+
+
+ ไม่รู้จักลายนิ้วมือ ลองอีกครั้ง
+
+
+ ขยับนิ้วเร็วเกินไป ลองอีกครั้ง
+
+
+ แสดงข้อเสนอแนะการค้นหา?
+
+
+ เพื่อรับข้อเสนอแนะ %1$s จำเป็นต้องส่งสิ่งที่คุณพิมพ์ในแถบที่อยู่ไปยังเครื่องมือค้นหา
+
+
+ ไม่
+
+
+ ใช่
+
+
+ เครื่องมือค้นหาบางตัวไม่สามารถแสดงข้อเสนอแนะ
+
+
+ ยกเลิก
+
+
+
+
+ ไซต์ทำงานอย่างไม่คาดคิด?\n ลองปิดการป้องกันการติดตาม
+
+
+ เพิ่มไปยังหน้าจอหลัก]]>
+
+
+ เปิดทุกลิงก์ใน %1$s\n ตั้ง %1$s เป็นเบราว์เซอร์เริ่มต้น
+
+
+ เติม URL โดยอัตโนมัติสำหรับไซต์ที่คุณใช้มากที่สุด\n แตะค้างที่ URL ใด ๆ ในแถบที่อยู่
+
+
+ เปิดลิงก์ในแท็บใหม่\n แตะค้างที่ลิงก์ใด ๆ ในหน้า
+
+
+ ปิดเคล็ดลับในหน้าจอเริ่มต้น
+
+
+ เปิดแท็บใหม่แล้ว
+
+
+ สลับ
+
+
+ เข้าสู่โหมดเต็มหน้าจอ
+
+
+ สลับไปยังลิงก์ในแท็บใหม่ทันที
+
+
+ ปิดกั้นไซต์ที่อาจเป็นอันตรายและหลอกลวง
+
+ ปิดกั้นไซต์หลอกลวงและรุกราน, ไซต์ที่มีมัลแวร์ และไซต์ที่มีซอฟต์แวร์ไม่พึงประสงค์ตามการรายงาน
+
+
+ โหมด HTTPS-Only
+
+
+ พยายามเชื่อมต่อกับไซต์ต่าง ๆ โดยใช้โปรโตคอลการเข้ารหัส HTTPS โดยอัตโนมัติเพื่อเพิ่มความปลอดภัย
+
+
+ ข้อยกเว้น
+
+ คุณได้ปิดใช้งานการปิดกั้นเนื้อหาสำหรับเว็บไซต์เหล่านี้
+
+ เอาออก
+
+ เอาเว็บไซต์ทั้งหมดออก
+
+
+ ปิดกั้นคุกกี้
+
+
+ คุณต้องการปิดกั้นคุกกี้หรือไม่?
+
+
+ แท็บขัดข้อง
+
+ ขออภัย เรากำลังมีปัญหากับแท็บนี้
+
+ ในฐานะเบราว์เซอร์ส่วนตัว เราไม่เคยบันทึกและไม่สามารถเรียกคืนแท็บนี้ได้
+
+ ปิดแท็บ
+
+
+
+
+
+ ส่งรายงานข้อขัดข้องไปยัง Mozilla
+
+
+
+
+ ตัวติดตามถูกปิดกั้นตั้งแต่ %s
+
+ เนื้อหา
+
+ โฆษณา
+
+ กลุ่มสังคม
+
+ การวิเคราะห์
+
+ การป้องกันการติดตามแบบพิเศษ
+
+ การป้องกันปิดอยู่สำหรับไซต์นี้
+
+ การป้องกันเปิดอยู่สำหรับไซต์นี้
+
+ การเชื่อมต่อปลอดภัย
+
+ การเชื่อมต่อไม่ปลอดภัย
+
+ ตัวติดตามและสคริปต์ที่จะปิดกั้น
+
+
+ ย้อนกลับ
+
+
+
+ ลบ
+
+
+ เปลี่ยนชื่อ
+
+ เปลี่ยนชื่อ
+
+
+ ชื่อทางลัด
+
+
+ <b>จะไม่ลบ</b>ภาพที่บันทึกไว้และแบ่งปันเมื่อคุณล้างประวัติของ %1$s
+
+
+
+ ชุดตกแต่ง
+
+ สว่าง
+
+ มืด
+
+ กำหนดโดยตัวประหยัดแบตเตอรี่
+
+ ตามชุดตกแต่งอุปกรณ์
+
+
+ ไซต์นี้ไม่รองรับ HTTPS
+
+
+ เรียนรู้เพิ่มเติม
+ โปรดเปลี่ยนการตั้งค่านี้ใน การตั้งค่า > ความเป็นส่วนตัวและความปลอดภัย > ความปลอดภัย]]>
+
+
+ การเชื่อมต่อไม่ปลอดภัย
+
+
+
+ หากคุณเคยเชื่อมต่อกับเซิร์ฟเวอร์นี้สำเร็จมาก่อน ข้อผิดพลาดอาจเกิดขึ้นเพียงชั่วคราว
+ ]]>
+
+
+ อาจมีใครบางคนกำลังพยายามปลอมแปลงไซต์นี้และคุณไม่ควรดำเนินการต่อ
+
+ %1$s ไม่เชื่อถือ %2$s เนื่องจากไม่ทราบผู้ออกใบรับรอง ใบรับรองถูกลงชื่อด้วยตนเอง หรือเซิร์ฟเวอร์ไม่ส่งใบรับรองระดับกลางที่ถูกต้องมาให้
+ ]]>
+
+
+
+ ปิดแท็บ
+
+
+
+ จับได้แล้ว! เราได้หยุดไซต์นี้ไม่ให้สอดแนมคุณแล้ว แตะโล่เพื่อดูข้อมูลเกี่ยวกับสิ่งที่เรากำลังปิดกั้น
+
+
+ ปิดป๊อปอัป
+
+
+
+ คุณได้รับการคุ้มครอง!
+
+ การตั้งค่าเริ่มต้นเหล่านี้ให้การปกป้องที่แข็งแกร่ง แต่คุณสามารถปรับแต่งการตั้งค่าให้ตรงกับความต้องการของคุณได้อย่างง่ายดาย
+
+ ปิด
+
+
+ แตะที่นี่เพื่อทิ้งทุกอย่าง — ประวัติ คุกกี้ ทุกอย่าง — และเริ่มต้นใหม่ในแท็บใหม่
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ ปิด
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ วิดเจ็ตการค้นหา
+
+ !-- This is the title of promote search widget dialog. -->
+
+ ล้างประวัติการท่องเว็บแล้ว! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ เริ่มวาระการท่องเว็บแบบส่วนตัว แล้วเราจะปิดกั้นตัวติดตามและสิ่งไม่พึงประสงค์ในขณะที่คุณใช้งาน
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ เราจะปล่อยให้คุณท่องเว็บแบบส่วนตัว แต่เริ่มต้นในครั้งต่อไปเร็วขึ้นด้วยวิดเจ็ต %1$s บนหน้าโฮมของคุณ
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ เพิ่มวิดเจ็ตไปที่หน้าจอหลัก
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ เพิ่มวิดเจ็ตในหน้าจอหลักแล้ว
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-tr/strings.xml b/mobile/android/focus-android/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000000..e3b17907c9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,1127 @@
+
+
+
+
+
+
+
+
+ Vazgeç
+
+ Tamam
+
+ Kaydet
+
+
+ Arama yapın veya adres yazın
+
+ Otomatik gizli gezinti.\nGez ve sil. Hepsi bu kadar.
+
+
+ Gezinti geçmişiniz silinmiştir.
+ Gezinti geçmişi temizlendi
+
+
+ Sekmenin gezinti geçmişi silinmiştir.
+
+
+ %1$s terimini ara
+
+
+ Paylaş…
+
+
+ Siteyle ilgili sorun bildir
+
+
+ %1$s ile aç
+
+
+ Birlikte aç…
+
+
+ Ana ekrana ekle
+
+
+ Kısayollara ekle
+
+ Kısayollardan kaldırıldı
+
+
+ Ayarlar
+ Hakkında
+ Yardım
+ Haklarınız
+
+
+ Takip kodlarını engelle
+
+
+ Bunu kapatmak bazı site sorunlarını düzeltebilir
+
+
+ İçerik engelleme
+
+ Bazı siteleri düzeltmek için kapatın
+
+
+ %1$s
+
+
+ Paylaş
+
+ Gezinti geçmişi silinsin mi?
+ Gezinti geçmişinizi güvenli bir şekilde silmek için bu bildirime dokunun veya bildirimi temizleyin.
+
+
+ Gezinti geçmişinizi güvenli bir şekilde silmek için bu bildirime dokunun veya bildirimi kaydırın.
+
+ Gezinti geçmişini sil
+
+
+ Aç
+
+
+ Sil ve aç
+
+
+ Sil
+
+
+ Gezinti geçmişini sil
+
+
+
+ Sil ve aç
+
+
+ Sil ve %1$s’u aç
+
+
+
+ Focus’ta ara
+
+ Klar’da ara
+
+ Focus Beta’da ara
+
+ Focus Nightly’de ara
+
+
+ %1$s kontrolü size verir.
+Onu gizlilik odaklı bir tarayıcı olarak kullanabilirsiniz:
+
+ Doğrudan uygulamada arama yapabilir ve web’de gezinebilirsiniz
+ Takip kodlarını engelleyebilirsiniz (veya belli takip kodlarına izin verebilirsiniz)
+ Tek düğmeyle çerezleri, arama ve gezinti geçmişinizi silebilirsiniz
+
+
+%1$s Mozilla tarafından geliştirilmiştir. Misyonumuz daha sağlıklı ve açık bir internet yaratmaktır.
+Daha fazla bilgi alın
]]>
+
+
+ Gizlilik ve Güvenlik
+
+
+ İzlenme, çerezler, veri tercihleri
+
+
+ Varsayılan yap, otomatik tamamlama
+
+
+
+
+ %1$s hakkında, yardım
+
+
+ Gelişmiş izlenme koruması
+
+
+ Web içeriği
+
+
+ Uygulama geçişi
+
+
+ Genel
+
+
+ Varsayılan tarayıcı, dil
+
+
+ Veri toplanması ve kullanımı
+
+ Arama
+
+
+ Arama önerilerini göster
+
+ %1$s, adres çubuğuna yazdıklarını arama motorunuza gönderecektir
+
+
+ Varsayılan
+
+
+ Arama motoru
+
+
+ Açık
+
+
+ Kapalı
+
+
+ Otomatik adres tamamlama
+
+
+ Popüler sitelerde
+
+
+ %s’un 450’den fazla popüler adresi adres çubuğunda tamamlaması için bunu etkinleştirin.
+
+
+ Eklediğim sitelerde
+
+
+ %s’un sık kullandığınız adresleri otomatik tamamlaması için etkinleştirin.
+
+
+ Siteleri yönet
+
+
+ Siteleri yönet
+
+
+ + Özel adres ekle
+
+
+ Otomatik tamamlama listeniz:
+
+
+ Adres ekle
+
+
+ Özel adres ekle
+
+
+ Özel adres ekle
+
+
+ Bağlantıyı otomatik tamamlamaya ekle
+
+
+ Çerezler ve site verileri
+
+
+ Veri tercihleri
+
+
+ Özel adresleri sil
+
+
+ Daha fazla bilgi al
+
+
+ Otomatik tamamlanacak özel adresleri ekleyin ve yönetin.
+
+
+ Eklenecek adres
+
+
+ Adresi yapıştırın veya yazın
+
+
+ Örnek: mozilla.org
+
+
+ Örnek: example.com
+
+
+ Yeni özel adres eklendi.
+
+
+ Sil
+
+
+ Sil
+
+
+ Yazdığınız adresi bir daha kontrol edin.
+
+ Dil
+
+ Sistem varsayılanı
+
+ Gizlilik
+ Reklam takip kodlarını engelle
+ Bazı reklamlar, siz onlara tıklamasanız bile ziyaret ettiğiniz siteleri takip eder
+ Analitik takip kodlarını engelle
+ Dokunma ve kaydırma gibi etkinlikleri kaydetmek, çözümlemek ve ölçmek için kullanılır
+ Sosyal takip kodlarını engelle
+ Sosyal medyada paylaşım düğmeleri, ziyaret ettiğiniz siteleri de takip eder
+ Diğer içerik takip kodlarını engelle
+ Bunu açarsanız bazı sayfalar olması gerektiği gibi çalışmayabilir
+ Çerezleri engelle
+
+
+ Hayır
+ Yalnızca üçüncü taraf takip çerezlerini engelle
+ Yalnızca üçüncü taraf çerezlerini engelle
+
+ Siteler arası çerezleri engelle
+ Evet
+
+
+ Uygulama kilidini açmak için parmak izimi kullan
+
+
+ Kısayollar eklediysem veya %s tarayıcısında bir web sitesi açıksa kilidi parmak iziyle aç.
+
+
+ Görünmezlik
+
+ Uygulama değiştirirken web sayfalarını gizle ve ekran görüntüsü almayı engelle.
+
+ Güvenlik
+
+ Performans
+ Web fontlarını engelle
+
+ Simge ve resimlerin görünmemesine yol açabilir
+
+ JavaScript’i engelle
+
+ Sayfalar daha hızlı yüklenebilir ama sorun da çıkarabilirler
+
+
+ %1$s’u varsayılan tarayıcım yap
+
+ Mozilla
+ Kullanım verilerini gönder
+
+
+ Daha fazla bilgi al
+
+
+ Mozilla, yalnızca %1$s’u daha da geliştirmek için ihtiyaç duyduğumuz verileri toplar.
+
+
+ Gizlilik bildirimi
+
+
+ Lisans bilgileri
+
+
+ Kullandığımız kitaplıklar
+
+
+ %s | OSS Kitaplıkları
+
+
+ %1$s hakkında
+
+
+ Yüklü arama motorları
+
+
+ Arama motorunu seç
+
+
+ Varsayılan arama motorlarını geri yükle
+
+
+ + Başka bir arama motoru ekle
+ Arama motorlarını sil
+ Sil
+
+
+ Başka bir arama motoru ekle
+
+ Tercih ettiğiniz motoru seçin:
+
+
+ Arama motoru ekle
+
+ Arama motoru adı
+ Kullanılacak arama dizgisi
+ Kaydet
+
+
+ Örnek: example.com/search/?q=%s
+
+ Yeni arama motoru eklendi.
+
+ Arama motoru adını yazın
+ Yüklenmiş bir arama motoru zaten bu adı kullanıyor.
+
+ Arama dizgisini yazın
+
+ Arama dizgisinin örnek biçimle uyumlu olduğundan emin olun
+
+
+ Girdiyi temizle
+
+
+ Kapat
+
+
+ Gezinti geçmişini sil
+
+
+ Açık sekmeler: %1$s
+
+
+ Güvenli bağlantı
+
+
+ Yükleniyor
+
+
+ Web sitesi yüklendi
+
+
+ Diğer seçenekler
+
+
+ Diğer seçenekler düğmesi
+
+
+ İleri git
+
+
+ Siteyi tazele
+
+
+ Geri git
+
+
+ Siteyi yüklemeyi durdur
+
+
+ Önceki uygulamaya dön
+
+
+ Engellenen takip kodu sayısı
+
+
+ Takip kodlarını engelle
+
+ Haklarınız
+
+ Bağlantıyı farklı uygulamada aç
+
+ Bu bağlantıyı %2$s ile açmak için %1$s\'tan çıkabilirsiniz.
+
+ Bağlantıyı açabilecek bir uygulama bul
+
+ Cihazınızdaki uygulamalar bu bağlantıyı açamıyor. %2$s mağazasında uygun bir uygulama aramak için %1$s\'tan çıkabilirsiniz.
+
+ Gizli Gezinti\'den çıkılsın mı?
+
+
+ %1$s indirildi
+
+
+ Aç
+
+
+
+
+
+
+
+
+
+
+ Kısayollara eklendi
+
+ Sunucu bulunamadı
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Kapat
+
+
+
+ %1$s tarayıcısına hoş geldiniz
+
+
+ Hızlı. Gizli. Sade.
+
+
+ Başlayın
+
+
+
+ %1$s diğer tarayıcılara benzemez
+
+
+ Daha fazla gizlilik için uygulamayı kapattığınızda geçmişinizi temizliyoruz.
+
+
+
+ Açtığınız her bağlantıda verilerinizi korumak için %1$s’u varsayılan tarayıcınız yapın.
+
+
+ Varsayılan tarayıcı yap
+
+
+ Atla
+
+
+
+ Gizliliğini güçlendir
+
+ Gizli gezintiyi daha da geliştirdik. Gezdiğiniz siteleri takip eden ve sayfaların yüklenmesini yavaşlatan reklamları ve diğer içerikleri engelleyebilirsiniz.
+
+
+ Senin araman, senin kararın
+
+ Başka bir şey mi arıyorsunuz? Ayarlar menüsünden varsayılan arama motorunuzu değiştirebilirsiniz.
+
+
+ Kısayolları ana ekranına ekle
+
+ %1$s’ta sevdiğiniz sitelere çabucak ulaşabilirsiniz. %1$s menüsünden \"Ana ekrana ekle\"yi seçmeniz yeterli.
+
+
+ Hep gizli kal
+
+ %1$s’u varsayılan tarayıcınız yapın, diğer uygulamalarda web sayfalarını açtığınız zaman da gizli gezintiden yararlanın.
+
+ Anladım!
+ Geç
+ İleri
+
+
+ -
+
+
+ Ekle
+
+
+ EVET
+
+
+ İptal
+
+
+ HAYIR
+
+
+ Kısayolu kullandığınızda gelişmiş izlenme koruması kapalı kalacaktır
+
+
+ Gizli gezinti oturumu
+
+
+ Bildirimler sayesinde %1$s oturumunuzu tek dokunuşla silebilirsiniz. Uygulamayı açmanıza ve tarayıcınızdaki açık sayfaları görmenize gerek kalmaz.
+
+
+ Gezinti geçmişini sil
+
+
+ Firefox’u indir
+
+
+
+
+
+
+
+
+ Mozilla Kamu Lisansı ve diğer açık kaynak lisanslarının koşullarıyla sunulmaktadır.]]>
+
+
+ buraya bakabilirsiniz.]]>
+
+
+ lisansları ile sunulmaktadır.]]>
+
+
+ bu çalışmalar GNU Kamu Lisansı v3 ile sunulmaktadır.]]>
+
+
+ Kullanıcı adı
+ Parola
+ Temizle
+
+
+
+ Güvenli bağlantı
+ Güvensiz bağlantı
+
+ Doğrulayan: %1$s
+
+
+ Site güvenliği
+ Adres zaten mevcut
+
+
+ Sayfada bul
+
+
+ Sayfada bul
+
+
+ %1$d/%2$d
+
+ %2$d sonuçtan %1$d. sonuç
+
+
+ Sonraki sonucu bul
+
+ Önceki sonucu bul
+
+ Sayfa bulmayı kapat
+
+
+
+
+ Masaüstü sitesini iste
+
+
+ Masaüstü sitesi
+
+
+ Adres kopyalandı
+
+
+ Geliştirici araçları
+
+
+ Bağlantıları uygulamalarda aç
+
+
+ Gelişmiş
+
+
+ Site izinleri
+
+
+ Çerez bildirimlerini azalt
+
+
+ Açık
+
+
+ Kapalı
+
+
+ Çerez bildirimlerini azalt
+
+
+ Mümkün olduğunda çerez isteklerini otomatik olarak reddederek daha az çerez bildirimi görün.
+
+ -->
+ Çerez bildirimlerini azalt
+
+
+ Bu sitede AÇIK
+
+
+ Bu site şu anda desteklenmiyor
+
+
+ Bu sitede KAPALI
+
+
+ Çerez bildirimlerini azalt
+
+
+ Bu sitede KAPALI
+
+
+ Bu sitede AÇIK
+
+
+ %1$s için çerez bildirimlerini azaltma açılsın mı?
+
+
+ %1$s için çerez bildirimlerini azaltma kapatılsın mı?
+
+
+ %1$s bu sitenin çerezlerini temizleyip sayfayı tazeleyecek. Tüm çerezlerin temizlenmesi oturumunuzu kapatabilir veya alışveriş sepetlerinizi boşaltabilir.
+
+
+ %1$s çerez isteklerini otomatik olarak reddetmeyi deneyebilir.
+
+
+ Çerez bildirimlerini azaltma özelliği henüz bu siteyi desteklemiyor. Ekibimizin bu siteyi inceleyip gelecekte desteklemesini ister misiniz?
+
+
+ Vazgeç
+
+
+ Destek iste
+
+
+ Destek sitesine istek gönderildi.
+
+
+ Destek sitesine istek gönderildi.
+
+
+
+ %1$s sinir bozucu çerez bildirimlerini kapatmak için çerez isteklerini reddetmeye çalışır.\n\nÇerez bildirimi tercihlerinizi %2$s ekranından değiştirebilirsiniz.
+
+
+ ayarlar
+
+
+ Otomatik oynatma
+
+
+ İzin vermek için:
+
+
+ 1. Android ayarlarına gidin
+
+
+ İzinler’e dokunun]]>
+
+
+ Ayarlara git
+
+
+ %1$s ayarını AÇIK yapın]]>
+
+
+ Kamera
+
+
+ Mikrofon
+
+
+ Konum
+
+
+ Bildirim
+
+
+ DRM denetimli içerikler
+
+
+ İzin iste
+
+
+ Engellendi
+
+
+ İzin verildi
+
+
+ Android tarafından engellendi
+
+
+ Ses ve videoya izin ver
+
+
+ Yalnızca sesi engelle
+
+
+ Önerilen
+
+
+ Ses ve videoyu engelle
+
+
+ Araştırmalar
+
+
+ Firefox zaman zaman araştırmalar yükleyip çalıştırabilir.
+
+
+ Daha fazla bilgi al
+
+
+ Değişiklikleri uygulamak için uygulama kapanacak
+
+
+ Kaldır
+
+
+ Etkin
+
+
+ Tamamlandı
+
+
+ USB/Wi-Fi ile uzaktan hata ayıklama
+
+
+ Kilidi aç
+
+
+ Parmak izinizle onaylayın
+
+
+ Mevcut uygulama oturumunuza devam etmek için parmak izinizi kullanabilirsiniz.
+
+
+ Bağlantıyı yeni oturumda aç
+
+
+ Parmak izi simgesi
+
+
+ Parmak izi tanınamadı. Yeniden deneyin.
+
+
+ Parmağınızı çok hızlı hareket ettirdiniz. Yeniden deneyin.
+
+
+ Arama önerileri gösterilsin mi?
+
+
+ Arama önerileri alabilmeniz için %1$s’un adres çubuğuna yazdığınız şeyleri seçtiğiniz arama motoruna göndermesi gerekir.
+
+
+ Hayır
+
+
+ Evet
+
+
+ Bazı arama motorlarında öneriler gösterilemez.
+
+
+ Kapat
+
+
+
+
+ Düzgün görünmeyen bir site mi var?\n
+ İzlenme Koruması’nı kapatmayı deneyin
+
+
+ Ana ekrana ekle]]>
+
+
+ Her bağlantıyı %1$s ile açabilirsiniz\n
+ %1$s’u varsayılan tarayıcınız yapın
+
+
+
+ En çok kullandığınız sitelerin adreslerini otomatik tamamlayabilirsiniz\n
+ Adres çubuğundaki herhangi bir adrese basılı tutun
+
+
+
+ Bağlantıları yeni sekmede açabilirsiniz\n
+ Bir sayfadaki herhangi bir bağlantıya uzun basın
+
+
+
+ Başlangıç ekranında ipuçlarını kapat
+
+
+ Yeni sekme açıldı
+
+
+ Geç
+
+
+ Tam ekran moduna geçiliyor
+
+
+ Hemen yeni sekmedeki bağlantıya geç
+
+
+ Olası tehlikeli ve aldatıcı siteleri engelle
+
+ Bildirilen aldatıcı ve saldırı amaçlı siteleri, kötü amaçlı yazılım sitelerini ve istenmeyen yazılım sitelerini engelle.
+
+
+ Yalnızca HTTPS modu
+
+
+ Daha fazla güvenlik için sitelere otomatik olarak HTTPS şifreleme protokolüyle bağlanmaya çalışır.
+
+
+ İstisnalar
+
+ Bu web sitelerinde içerik engellemeyi kapattınız.
+
+ Kaldır
+
+ Tüm web sitelerini kaldır
+
+
+ Çerezleri engelle
+
+
+ Çerezleri engellemek ister misiniz?
+
+
+ Sekme çöktü
+
+ Kusura bakmayın, bu sekmeyle ilgili bir sorun yaşıyoruz.
+
+ Gizlilik odaklı bir tarayıcı olarak sekmelerinizi asla kaydetmediğimiz için geri getirmemiz de mümkün değil.
+
+ Sekmeyi kapat
+
+
+
+
+
+ Çökme raporunu Mozilla’ya gönder
+
+
+
+
+ %s tarihinden beri engellenen takip kodu
+
+ İçerik
+
+ Reklam
+
+ Sosyal
+
+ Analitik
+
+ Gelişmiş izlenme koruması
+
+ Bu sitede korumalar KAPALI
+
+ Bu sitede korumalar AÇIK
+
+ Bağlantı güvenli
+
+ Bağlantı güvenli değil
+
+ Engellenecek takip kodları ve betikler
+
+
+ Geri dön
+
+
+
+ Kaldır
+
+
+ Yeniden adlandır
+
+ Yeniden adlandır
+
+
+ Kısayol adı
+
+
+ %1$s geçmişini sildiğinizde, kaydettiğiniz ve paylaştığınız resimler <b>silinmeyecektir</b>
+
+
+
+ Tema
+
+ Açık
+
+ Koyu
+
+ Pil tasarrufuna göre
+
+ Cihaz temasına uy
+
+
+
+ Bu site HTTPS’i desteklemiyor
+
+
+ Daha fazla bilgi alın
+ Bu ayarı Ayarlar > Gizlilik ve Güvenlik > Güvenlik kısmından değiştirebilirsiniz.]]>
+
+
+ Bağlantı güvenli değil
+
+
+
+ Daha önce bu sunucuya sorunsuz bağlandıysanız sorun geçici olabilir.
+ ]]>
+
+
+ Birisi bu siteyi taklit etmeye çalışıyor olabilir ve devam etmeniz riskli olabilir.
+
+ %1$s %2$s sitesine güvenmiyor. Sitenin sertifikasını düzenleyen kuruluş tanınmıyor olabilir, sertifika kendi kendine imzalanmış olabilir veya sunucu doğu ara sertifikaları göndermiyor olabilir.
+ ]]>
+
+
+
+ Sekmeyi kapat
+
+
+
+ Bu sitenin sizi gözetlemesini engelledik. Engellediğimiz şeyleri görmek isterseniz kalkana dokunun.
+
+
+ Açılır pencereyi kapat
+
+
+
+ Koruma altındasınız!
+
+ Bu varsayılan ayarlar güçlü koruma sağlar ama isterseniz ayarları ihtiyaçlarınızı göre değiştirebilirsiniz.
+
+ Kapat
+
+
+ Geçmişi ve çerezleri temizleyip yeni bir sekmede yeni bir başlangıç yapmak için buraya dokunun.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Kapat
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Arama widget’ı
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Gezinti geçmişi temizlendi! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Gizli gezinti oturumunuzu başlatın. Siz gezinirken takip kodlarını ve diğer istenmeyen içerikleri engelleyeceğiz.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ %1$s widget’ını ana ekranınıza ekleyerek gizli gezintilerinize daha hızlı bir başlangıç yapabilirsiniz.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Widget’ı ana ekrana ekle
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Widget ana ekrana eklendi
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-trs/strings.xml b/mobile/android/focus-android/app/src/main/res/values-trs/strings.xml
new file mode 100644
index 0000000000..523bc827f0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-trs/strings.xml
@@ -0,0 +1,1090 @@
+
+
+
+
+
+
+
+
+ Duyichin\'
+
+ Ga\'ue
+
+ Na\'nïnj sà\'
+
+
+ Gachrun nuguan\' ruhuât nanà\'uìt
+
+ Gache nu huì\' nanûn man\'an. Ni\'iaj. Nadure\'. yakaj da\'nga\' ñû.
+
+
+ Gisij ganare\' nej nuguan\' garajsunt nga gaché nunt.
+ Nare\' nej nuguan\' garajsun rè\' nga nana\'ui\' rè\'
+
+
+ Gisij ganare\' nej nuguan\' garajsunt nga gaché nunt riña rakïj ñanj.
+
+
+ Nana\'uì\' %1$s
+
+
+ Ga\'ninj gan\'an riña duguî\'t…
+
+
+ Natà’ sa ‘iaj sun a’nan’ riña sitiô na
+
+
+ Na\'nïn ngà %1$s
+
+
+ Na\'nïn…
+
+
+ Na\'nïn riña pantayâ nìko
+
+
+ Nachrūn riña nù aksêso dirêckto
+
+ nadure\' riña aksêso dirêckto
+
+ Nagi\'iaj
+ Doj rayi\'î
+ Sa ruguñu\'unj un
+ Nej sa tna\'uej rayi\'ît
+
+
+ Narun\' sa naga\'naj ñun\'
+
+
+ Sisi gi\'iaj desaktibandôt nan ni ga\'ue nahuin da\'aj sa hua a\'nan\' hiuj nan
+
+
+ Garrun\' kontenido
+
+ Duna\'aj da\' ga\'ue nahuin da\'aj sîtio
+
+
+ Rugujñu\'unj %1$s
+
+
+ Ga\'nïnj gan\'an ngà
+
+
+ Narè\' nej nuguan\' garajsunt nga ganana\'uî\'t
+
+
+ Na\'nïn
+
+
+ Nadure\' ni na\'nïnt
+
+
+ Nadure\'
+
+
+ Narè\' nej nuguan\' garajsunt nga ganana\'uî\'t
+
+
+
+ Dure\' ni na\'nint
+
+
+ Nadure\' ni na\'nïnt %1$s
+
+
+
+ Nānà’huì’ riña Focus
+
+ Nānà’huì’ riña Klar
+
+ Nānà’huì’ riña Focus Beta
+
+ Nānà’huì’ riña Focus Nightly
+
+
+ %1$s sò\' \'nïnj rā\'a daran\' sa \'iát.
+Nāgi\'iaj \'ngō nabegado huìi man:
+
+ Nānà\'uì\' nī gāchē nu āsìj riña aplikasiun dan
+ Nārán riña nej sa naga\'nāj a (asi nāgi\'iaj nākàt man sisī dādan achín man)
+ Nādure\' nej koki nī nej sa atāj na\'ānj danè\' gaché nunt
+
+
+%1$s si rāsūn Mozilla huin man. Nù huin ñunj da\' gā ni\'nïnj internter riñant.
+Gāhuin chrūn doj
]]>
+
+
+ Sa huìi nī sa dugumin
+
+
+ Nana\'ui\', cookies, ganahuij datos
+
+
+ Ganahui\' gahuin ma sa yitïnj in
+
+
+
+
+ Rayi\'î %1$s, rugujñu\'unj
+
+
+ Sa hua hue\'ê doj guendâ nará riña sa naga\'naj a
+
+
+ Sa nu riña web
+
+
+ Naduno\' aplikasion
+
+
+ Da\'ua nguéj
+
+
+ Nabegado hua niñaa, nuguan\'an
+
+
+ Nagi\'iaj chre\' ma datos
+
+ Nana\'uì\'
+
+
+ Nahuin\' ro\'ô\' sa ruguñu\'unj nana\'ui\'
+
+ %1$s gani\' ga\'anj sa achrun\' riña dukuán direksion riña sa nana\'ui\'
+
+
+ Ngà hua niñanj
+
+
+ Sa nana\'ui\'i
+
+
+ On
+
+
+ Off
+
+
+ Nuta\' ma\'ān ma URL
+
+
+ Guendâ nej sitiô hua hue\'ê doj
+
+
+ Nāchrūn man %s da’ nūtà\’ mān\’an man 450 URLs sa nīhià\’ ruhuâ ni gīni\’iāj ni.
+
+
+ Guendâ nej sitiô nuta\'t huajt
+
+
+ Nāchrūn rasūn nan %s da’ nāhuin chrē’ si URLs.
+
+
+ Dugumî nej sîtio
+
+
+ Dugumî nej sîtio
+
+
+ + Nùto\' sa nagi\'iaj ma\'ān URL
+
+
+ Yi’nïn’ nuguan’ ngà nañû mān’an:
+
+
+ Gachrun a\'ngo URL
+
+
+ Nùto\' sa nagi\'iaj ma\'ān URL
+
+
+ Nùto\' sa nagi\'iaj ma\'ān URL
+
+
+ Nachrun enlasê ruhuât natu ma\'an
+
+
+ Kookies ni si dato sitio
+
+
+ Ganahui nej datos
+
+
+ Nadure\' sa nagi\'iaj ma\'ān URL
+
+
+ Gahuin chrūn doj
+
+
+ Noto\' sa nagi\'iaj ma\'ān daran\' URL.
+
+
+ URL nuto\'
+
+
+ Gàchru\' URL
+
+
+ Nagi\'io\': mozilla.org
+
+
+ Dànaj gachrunt: example.com
+
+
+ URL naka doj nga nata\'
+
+
+ Guxūn
+
+
+ Guxūn
+
+
+ Ni\'iaj si hua hue\'è URL gachrunt.
+
+ Nânj (Nânj a\'mi\')
+
+ Sistema ngà hua niñaa
+
+ Nitaj à\'go ni\'iaj sò\'
+ Si gâ\'nïnjt nej anûnsio
+ Hua dà\'aj nej sa dugune\' sitiô nan ni ga\'ue gini\'ìn nej si, si sô atuj
+ Si gâ\'nïnjt nej blokeador analítiko
+ Si sunj huin nagi\'iaj chrej, nadigi\'ñunj daj unanj ma
+ Si gâ\'nïnjt nej blokeador sosial
+ Hua ni\'inj riña sitiô na da\' gini\'in si so\' àtuj ni digàn botûn da\' duyi\'ngo\'
+ Si gâ\'nïnt a\'ngô nej sa ni\'iaj sa \'iát
+ Si ganachunt man, ni gahuin ni si gi\'iaj sun hue\'e da\'aj pagina
+ Garun\' kookies
+
+
+ si ga\'ue, guruhuat
+ Garuun\' 3rd-party ma kookies nikò\' a\'ngô nej si
+ Garuun\' 3rd-party ma kookies
+ Nārán riña koki riña nej sîtio
+ Ga\'ue, gi\'iaj \'ngo sununj un
+
+
+ Garasun\' dej ro\'ô\' daj gatu\' riña aplikasion
+
+
+ Nā’nïn nga daa’nga’ ra’ât sisī nachrûnt akseso direkto asi huā ni’nïnj ‘ngō sitio riña %s.
+
+
+ Dugumî sa achrûnt
+
+ Nagi\'iaj huì nej pâjina si ruhuât garasunt a\'ngô app ni si ga\'ue nari\' riña pantayâ.
+
+ Sa dugumin
+
+ suun
+ Nará riña nej yi\'nïn\' web
+
+ Ga\'ue narugui\'ìj riña a\'ngô ikono
+
+ Garun\' riña JavaScript
+
+ Ga\'ue gi\'iaj sun hiò doj pajina sanin se da hue\'ê chre
+
+
+ Nagi\'iaj %1$s da\' garasun yitïnjt
+
+ Mozilla
+ Ga\'nïnj nej datô arâj sunt
+
+
+ Gini\'in doj rayi\'î na
+
+
+ Ûta ‘iaj nukuaj Mozilla man’an da’ gā hue’ê nej sa narikïj %1$s da’ garasun daran’ nê’.
+
+
+ Noticia huìi
+
+
+ Si nùguàn\' līsênsia
+
+
+ Nej dukuâ ñanj arâj sun ñûnj
+
+
+ %s Dukuâ ñanj OSS
+
+
+ Rayi\'î %1$s
+
+
+ Nej sa nana\'ui\'t gà\' nikajt
+
+
+ Nāguī sa rīñā ruhuât nānà’uì’t
+
+
+ Nagi\'iaj nakà nû nej sa nana\'uî\'t gà\' nikajt
+
+
+ Nachrun\' \'ngo sa huin ruhuô\' nanà\'uì\'
+ Nadure\' sa nana\'ui\'
+ Guxūn
+
+
+ Nūtà’ a’ngô sa rīñā ruhuât nānà’uì’t
+
+ Nāguī sa rīñā ruhuât nānà’uì’t:
+
+
+ Nutò\' sa nana\'uì\'
+
+ Si yugui sa nana\'ui\'
+ Ahuin si gahuin cadena gàrasunt
+ Na\'ninj sa\'
+
+
+ Da\' gi\'ìat: example.com/search/?q=%s
+
+ Ngà nata\' a\'ngo sa nakà da\' nana\'uì\'.
+
+ Si yugui sa nana\'ui\'
+ \'Ngò sa nana\'ui\' na nī hue ge danaj gu\'naj ma.
+
+ Duguatûj sa nanikò\' dugui\'i
+
+ Ni\'iaj ni\' si ru\' da\'uì gà nianj huin hua ma
+
+
+ Nadure\' sa duguatujt
+
+
+ Dunaj man
+
+
+ Narè\' nej nuguan\' garajsunt nga ganana\'uî\'t
+
+
+ Rakïj ñanj huā ni\'nïnj: %1$s
+
+
+ Koneksiôn hua hue\'ê
+
+
+ Hìaj ayì\'ij
+
+
+ Gayi\'ìj \'iaj sunj
+
+
+ Doj sa ga\'ue nagi\'át
+
+
+ Butûn \'na\' doj nej sa ga\'ue gi\'iát
+
+
+ Gache nu gan\'anjt ne\' ñaa
+
+
+ Nayi\'ì nakà ñû riña sîtio
+
+
+ Gache nu gan\'anj rukùt
+
+
+ Duyichin\' sa ayi\'ì sîtio
+
+
+ Nanikaj riña app garajsunt akuan\'
+
+
+ Daj nikò sa naga\'naj \'ngà duyichin\'
+
+
+ Nej rastreador gahuin blokeadô
+
+ Nej sa tna\'uej rayi\'ît
+
+ Na\'nïn link na riña a\'ngô app
+
+ Ga\'ue gan\'anjt riña %1$s da\' na\'nïnt na link riña %2$s.
+
+ Nana\'uì\' \'ngo app ga\'ue na\'nïn nuguan\' nan
+
+ À\' si \'ngo app nikajt a\'uej na\'nïn enlace nan. Ga\'ue gan\'anjt riña %1$s da\' nana\'uì\'t %2$s guendâ \'ngo app ga ni\'ñanj.
+
+ Gahui riña aché nu huìt
+
+
+ %1$s Nga ganahui
+
+
+ Na\'nïn
+
+
+
+
+
+
+
+
+
+
+ Ngà nanunj riña nej aksêso dirêkto!
+
+ Nu narì\'ij serbidor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nārán
+
+
+
+ Guruhuât gunumânt riña %1$s
+
+
+ Hìo, huā hûij. Nitāj sa dukuu ‘na’ riñanj.
+
+
+ Gāyi\'ì
+
+
+
+ Sê dàj rû\' \'hiaj a\'ngô nej sa nana\'hui\'i \'hiaj %1$s
+
+
+ Nadure’ ñûnj daran’ chre nej sa nâj ‘hiát ngà gisîj gaché nunt da’ sī naga’nāj nej si sò’.
+
+
+
+ Nā’nïnj %1$s da’ dugumîn man nej sa huā rayi’ît ngà na’nïnjt ‘ngō pajinâ nākàa.
+
+
+ Dūnâj gāhuin man riña sa nānà\'uì\' yītïnjt
+
+
+ Si gi\'iaj guendât
+
+
+ Nagi\'iaj nukuaj riña gache nun \'ngorïnt
+
+ Ga’ue gan’ānj ñānt doj ngà riña aché nu huìt. Dure’ asi narán riña nej sa ga’ue gatū naga’nāj nej sa ahī màn ‘iát nī natsij nej diû huin ràn nej pâjina ngà nayi’nïn.
+
+
+ Sa nana’uî’t, si chrējt
+
+ Huā a’ngô sa nana’uî’ raj. Naguī a’ngô sa rugûñu’ūnj da’ nana’uì’t sa huin ruhuât riña configuración.
+
+
+ Gachrūn nej acceso directo riña aga’ ayi’ì sinïnt
+
+ Nanikàjt riña nej sitiô nihià’ ruhuât %1$s nanèt chre. Ma sa gi’iát huin sisī guru’man ra’ât riña tāj \"Nachrun riña aga’ gayi’ì\" riña menú %1$s.
+
+
+ Yitïnj gachē nun ‘ngōrïnt
+
+ Nachrūn %1$s da’ gahuin man nevegador garasūn yitïnjt nī ga’ue garasunt man da’ gachē nu ‘ngorïnt ngà na’nïnt a’ngô da’āj nej pâjina.
+
+ Ngà nikà man an!
+ Si gi\'iaj guendât
+ Ne\' ñaan
+
+
+ -
+
+
+ Nutà\'
+
+
+ GĀ’HUE
+
+
+ Duyichin\'
+
+
+ SĪ GA’UE
+
+
+ Nāyi’nïn riña gātū nikô’ nikāj ‘ngō sa dugumîn ñù’ nga aché nun’ sanī huā na’àj man
+
+
+ Sesión riña gachē nu huìt
+
+
+ Nej notificación nī rugûñun’un da’ nadurê´t %1$s sesión ngà ‘ngō toke. Sê da guezâ na’nïnt riña app asi ni’iāj nùhuin si ‘iaj sun riña navegador hua ‘iát.
+
+
+ Narè\' nej nuguan\' garajsunt nga ganana\'uî\'t
+
+
+ Naduninj Firefox
+
+
+
+
+
+
+
+
+ si’hiaj Mozilla Nī a’ngô nej nuguan’ tna’huēj rayi’î nej sun nan.]]>
+
+
+ hiūj nan.]]>
+
+
+ nej lisênsia huā nî’nïnj ïn.]]>
+
+
+ GNU ‘iaj Licencia Yumàn’ v3, nī nunj hiūj nan .]]>
+
+
+ Si yugui re\'
+ Da\'nga\' huìi
+ Na\'nïn\'
+
+
+
+ Konexiôn yitinj in
+ Konexiôn nitaj si gua yitinj
+
+ Ngà ganatsij sa gu\'nàj: %1$s
+
+
+ Sa dugumi\'
+ URL sa gayi\'i sesión
+
+
+ Nana\'ui riñan ñanj
+
+
+ Nana\'ui\' riñan ñanj
+
+
+ %1$d/%2$d
+
+ %1$d si\'iaj %2$d
+
+
+ Nana\'ui\' a\'ngò sa \'na\'a
+
+ Sa \'ngà gachin
+
+ Narrun\' sa nana\'ui\'
+
+
+
+
+ Gachìn\' riña ñanj sinïï
+
+
+ Si lūgâ eskrītôrio
+
+
+ Ngà guxun\' URL
+
+
+ Rasun arajsun Firefox
+
+
+ Nā\'nīn nej lînk riña nej aplikasiûn
+
+
+ Sa huaj ñaan
+
+
+ Sa rikî nì\'iaj sîtio
+
+
+ Nadugua\' si Banêr Kôki
+
+
+ Nāchrūn
+
+
+ Dūnâ\’àj
+
+
+ Nadugua\' si Banêr Kôki
+
+
+ Nāran riña nej sa duyinga’ ‘ngō sa ane’e así ‘ngō rasuun nī sī gâ’nïn da’ gātū nej kokî nan.
+
+ -->
+ Nadugua\' si Banêr Kôki
+
+
+ HUĀ NÎ’NÏN guendâ sitiô nan
+
+
+ Huā sa gāchin da’ gā’hua nāyi’nïn sitiô nan
+
+
+ NITĀJ SI HUĀ NÎ’NÏN guendâ sitiô nan
+
+
+ Nadugua\' si Banêr Kôki
+
+
+ NITĀJ SI HUĀ NÎ’NÏN guendâ sitiô nan
+
+
+ HUĀ NÎ’NÏN guendâ sitiô nan
+
+
+ Nachrūn sa nadugua’ nej si banêr nej kôki guendâ %1$s aj
+
+
+ Dūnâ’àj sa nadugua’ nej si banêr nej kôki guendâ %1$s aj
+
+
+ %1$s nādure’ man daran’ nej kokî ‘na’ riña sitiô nan nī nāgi’hiaj nàkaj pâjina. Si nādure’ej daran’ nej kôki nī gā’hue nāran si sēsiûnt asi nādure’ej nej sa ruhuât gīrānt.
+
+
+ %1$s gāhue sī nahuin rā’a nej kokî ruhuât gā’nïnt gātū.
+
+
+ Akuan’ nï̄, nitāj si â’nï̀nj sitiô nan da’ nāgà’ si banêr nej kôki. Ruhuât gāchìnj nīhiát da’ nātsij ñûnj sitiô nan nī gā’nïn ñûnj nej kokî nan ne’ rūkù doj aj.
+
+
+ Dūyichin\'
+
+
+ Gāchìnj man
+
+
+ Ngà ga’nïn sa gachín nī’hiát gan’ānj.
+
+
+ Ngà ga’nïn sa gachín nī’hiát gan’ānj.
+
+
+
+ %1$s nù huin aran riña nej kokî avhín nì’hiaj gātū da’ si rañu’ūj nej si banêr kôki sò’.\n\nNādunā sa garan’ ruhuât guendâ nej si banêr kôki riña %2$s.
+
+ nāgi\'iô\'
+
+
+ Nānùn ma\'an man
+
+
+ Da\' ga\'hue gā\'nïn:
+
+
+ 1. Gūij riña gā\'hue nāgi\'iát Android
+
+
+ nej sa achín nì\'hiát]]>
+
+
+ Gūij riña gā\'ue Nāgi\'hiô\'
+
+
+ %1$s]]>
+
+
+ Kâmara
+
+
+ Aga\' uxun nanèe
+
+
+ Danè\' huin
+
+
+ Sa atāj nan\'ānj an
+
+
+ Sa màn nan nī sa ‘nï̄nj ra’a DRM huin
+
+
+ Gāchinj nì\'hiaj
+
+
+ Nitāj si hūaj nayi\'nin
+
+
+ Gā\'hue
+
+
+ Nitāj si hūaj nāyi\'nïn \'hiaj Android
+
+
+ Gā\'nïn gī\'hiaj sun sa unïnt ngà sa ni\'hiājt
+
+
+ Màn riña sa unïnt nārán
+
+
+ Sā sà\'a huin ânj
+
+
+ Nārán riña sa unïnt ngà sa ni\'iājt
+
+
+ Nej ēstûdio
+
+
+ Ngà gā’hue gā’nïnj man nî ngà gā’hue gāyi’ì nej man gī’hiaj man ēstûdio.
+
+
+ Gāhuin chrūn doj
+
+
+ Nārán riña aplikasiûn nan da’ gā’hue nātū sa nākàa riñanj
+
+
+ Nādure\'
+
+
+ Ngà \'hiaj sunj
+
+
+ Ngà gisîj
+
+
+ Guyun sa kïj hua nga USB/Wi-Fi
+
+
+ Na\'nïn riñanj
+
+
+ Gāchrūn da’nga’ nuu si huā nīkā ruhuât
+
+
+ Gārasun da’nga’ nū ‘hiát sisī ruhuât gān’āanjt ne’ ñāan ngà si sesiûn aplikasiûn nan.
+
+
+ Nā\'nīn lînk riña a\'ngô sesiûn nākàa
+
+
+ Da\'nga\' ra\'a
+
+
+ Na\'ue nani\'ìn ma da\'ngà ra\'at. garahue ñu.
+
+
+ Ûta hiò gutat ra\'at. Garahue ñu.
+
+
+ Ruhuât dīganj a\'ngô nuguan\' nikāj dugui\' ngà sa nana\'uî\' raj
+
+
+ Da’ nāhuin rā’ât ‘ngō nuguan’ rugūñu’ūnj un nī, %1$s gà’nïn sinūguàn’t gān’ānj riña motor de busqueda.
+
+
+ Si ga\'ue
+
+
+ Ga\'ue
+
+
+ Hua da\'j nej na nà\'na\'ui\' nī nū diganj da rugujñu\'unj
+
+
+ Si gi\'iaj guendo\'
+
+
+
+
+ Na\'ue si\'iaj sun hue\'ê sitio na an\'\nGuxun sa arrán guendà naga\'naj ma
+
+
+ Nuto\' ma riña pantayâ ayi\'ij]]>
+
+
+ Na\'nïn\' datan nej enlase riña %1$s\nNagi\'iaj %1$s navegador yitïnj in
+
+
+ "Nagi\'iaj chre\' nej URL guenda nej sitio rajsun yitïnjt\nRu\'ma ra\'à riña URL \'na\' riña dukuán direksiôn "
+
+
+ Na\'nïn a\'ngo enlase riña a\'ngo rakïj ñanj nakàa\nRu\'ma ra\'à riña ahuin nanj ma\'an enlase \'na riña pajinâ
+
+
+ Duna\'àj sa rikî sujerênsia riña pantayâ gayi\'ij
+
+
+ Ngà\' nayi\'nïnj a\'ngô rakij ñaj nakàa
+
+
+ Naduno\'
+
+
+ Hiàj nanunj da\' huā ngè riña aga\'a
+
+
+ Naduna riña dunikò\' duguî\'t rakïj ñanj ra\'ñanj chre
+
+
+ Narán riña nej sitiô diga\'ñu\'unj ni sa yi\'ìi
+
+ Narán riña nej sitiô yi\'ìi ni nej sa diga\'ñu\'unj, nej si sitiô malware ni nej sitiô software nitaj si ni\'ñanj an.
+
+
+ ‘Hiaj sunj màn ngà HTTPS
+
+
+ Huin ruhuaj gātu riña nej sitio ngà ‘ngō da’nga’ gū’nàj HTTPS dadin’ dugumîn da’nga’ nan doj daran’ nej sa huā ‘hiát.
+
+
+ Ga\'ue na\'nïnt
+
+ Guxun ngèt sa narán riña sa yi\'ìi guendâ nej sitiô nan.
+
+ Guxūn
+
+ Guxun daran\' nej sîtio
+
+
+ Nārûn\’ riña nej kôki
+
+
+ Nāránt riña nej kôki aj
+
+
+ Gire\' ngè rakïj ñanj
+
+ Si ga\'man ruhuât. Hua a\'nan\' \'ngo rakïj ñanj nan.
+
+ \'Ngo navegadô huìi huin ñûnj, ni si ga\'ue ni\'iajt sa nana\'ui\'t akuan\' riña rakïj ñanj nan.
+
+ Narán Rakïj ñanj
+
+
+
+
+
+ Gana\'nínj nuguan\'an nuhuin saj \'iaj sun a\'nan\' Mozilla
+
+
+
+
+ Ganarán riña nej sa naga\’nāj a āsìj %s
+
+ Sa màn
+
+ Sa duyinga\’ nuguan\’ an
+
+ Sa yūmàn’an
+
+ Sa nādigî’ñu
+
+ Sa hua hue\'ê doj guendâ nará riña sa naga\'naj a
+
+ NITĀJ SI \'IAJ SUN nej sa dugumî sò\' riña sitiô nan
+
+ Ngà \'IAJ SUN nej sa dugumî sò\' riña sitiô nan
+
+
+ Huā seguridâ nikāj koneksiôn
+
+ Nitāj seguridâ nikāj koneksiôn
+
+ Nej sa naga’nāj a nī nej skrip gā’ue nāránt riña
+
+
+ Nānīkāj ne\’ rūkùu
+
+
+
+ Nādure\'
+
+
+ Nāchrūn a\'ngô si yūgui
+
+ Nāchrūn a\'ngô si yūgui
+
+ Si yūgui aksêso dīrêkto
+
+
+ Nej ñadu’hua ngà na’nïn sà’t <b>sê sī</b> nārè’ nej man si ruhuât nādurê’t nej sa ni’hiājt ngà gaché nut riña %1$s
+
+
+
+ Têma
+
+ Rāngà\' man
+
+ Huā rūmin\' man
+
+ Nanûn man da\’ dūguminj batería
+
+ Gānikò\' si tema aga\' dan
+
+
+ Nitāj si aran’ sitiô nan ngà HTTPS
+
+
+ Gāhuin chrūn doj Nadunā dàj gā riña hiūj nan riña tàj sa nadunāt> Sa huìi & Sa dugumîn> Sa dugumîn.]]>
+
+
+ Nitāj seguridâ nikāj koneksiôn
+
+
+ Sisī nitāj si gi’hiaj chìj gatût riña servidor nan ganïnj ïn, nī sê sa ahīi huin, dòj nī nāhuin man.]]>
+
+
+ Huā ‘ngō sa yī’ì natût riña sitio nī da’huît gāhuīt dadin’ gā’hue gā’huì’ yī’ìj sòj.%1$s nitāj si nani’in %2$s man dadin’ nitāj si huā hia danè’ gurugui’ij, rû’huāj si sertifikâdo digañu’ūnj un huin man rû’ huaj, así na’hue gā’nïnj servidor sertifikadô huā hue’ê. ]]>
+
+
+
+ Narán rakïj ñanj
+
+
+
+ Hìaj â. Si gâ’nïn’ da’ nāga’nāj sitiô nan sò’. Gūru’man ra’a riña nù eskûdo si ruhuât gīni’înt ahuin sa yi’ì arán ñûnj.
+
+
+ Nārán riña sa guruhui’ nan
+
+
+
+ Ngà dugumînt sò’
+
+ Sa ngà huā nan nī arán man nī dugumîn man sò’. Si ruhuât nādunāt doj dàj gaj nī gā’hue gan’ānjt riña tāj sa nāgi’hiô’.
+
+ Si gi\'hiaj guendô\'
+
+
+ Gūru’man ra’a hiūj nan da’ nādurê’t daran’anj — sa gaché nunt, nej kôki, daran’anj — y nayi’ì nākà ngà a’ngô rikïj ñanj.
+
+
+
+
+ Nārán
+
+
+ Widget nana’hui’i
+
+
+ Nare\' nej nuguan\' garajsun rè\' nga nana\'hui\' rè\' 🎉
+
+
+ Gayi’ì si sēsiûnt guendâ gāchē nun huìt nī gārán ñûnj riña nej sa naga’nāj a nī a’ngô nej sa yī’ìi.
+
+
+ Dunâj ñûnj gāchē nun huìt, sanī ga’hue gāyi’ì râ’ñànjt doj riña %1$s widget nù riña pāntayâ ayì’ìt.
+
+
+ Nāchrūn widget riña pāntayâ ayì’ìt
+
+
+ Ngà nanûn widget riña pāntayâ ayì’ìt
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-tsz/strings.xml b/mobile/android/focus-android/app/src/main/res/values-tsz/strings.xml
new file mode 100644
index 0000000000..67b71e9b21
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-tsz/strings.xml
@@ -0,0 +1,947 @@
+
+
+
+
+
+
+
+
+ Áamkutaru
+
+ OK
+
+ Patsani
+
+
+ Patsa o miyukwani karanharhita
+
+ Jirikurhitini jirinhant\'ani.
+
+
+ Ampe mankari jirinhant\'apka mirikurhisti ya.
+
+ Kentitanhasti jirinhantskuechani
+
+
+ Jirinhantskata ampe kéntitanhasti ya.
+
+
+ %1$s Jirinhantsï
+
+
+ Kwankurhini…
+
+
+ Eyankpeni éska eranharhikwa xerekurhiska
+
+
+ %1$s Mítani
+
+
+ Mítani…
+
+
+ Wénakwarhu kúparhatani
+
+
+ Jurhikutakuecharhu kuparata
+
+ Jurhikutakuaecharhu kentita
+
+ Pérakurhikwaecha
+ Áamkuri
+ Jahroap\'erakwa
+ Sésikwaecha
+
+
+ Estakutichani kwarhukuani
+
+
+ Nántika jatsïntaaka xerekurhikwa ampe íni p\'átaparini
+
+
+ Jatakwa ampe kwarhukunhasti
+
+ P\'átani ka eranharhikwaechani jatsïnt\'ani
+
+
+ Winhaskukata %1$s jimpo
+
+
+ Ixusï kwankurhini
+
+
+ Jirinhantskwa ampe mirikurhini
+
+
+ Inchani
+
+
+ Kéntitani ka mítant\'ani
+
+
+ Kéntitani
+
+
+ Jirinhantskwani kéntita
+
+
+
+ Kéntitani & mítani
+
+
+ Kéntitani ka mítani %1$s
+
+
+
+ Focusiorhu jirinhantsi
+
+ Klariorhu jirinhantsi
+
+ Focus Betarhu jirinhantsi
+
+ Focus Nightlyrhu jirinhantsi
+
+
+ Kwatakwa & Jískakwa
+
+
+ Estakupani, exepakwa, erakukwaecha
+
+
+ Pént\'ani, karakurhikwani
+
+
+
+
+ Jarhoap\'ekwa ankueri %1$s,
+
+
+ Sesimintu kuatakua
+
+
+ Eranharhikwaeri Jatakwa
+
+
+ Úratarakwaechani Móptakuani
+
+
+ Yámu
+
+
+ Jirinhatarantskuarhu uantakuani, pentani
+
+
+ Jatakwa T\'antakwa & Úrani
+
+ Jirinhani
+
+
+ Arhistsïkwaechani exeani
+
+ %1$s ch\'eeti jirinhatarantskwarhu axati ampe mankari karanharhikuaka
+
+
+ Pént\'ani
+
+
+ Jirinhatarantskwa
+
+
+ Étskuni
+
+
+ P\'atani
+
+
+ URL Karanharhikuarini
+
+
+ Erakukuhikwaecha
+
+
+ Apakerani jimpoka %s karakurintaaka kari 450 URLs miyurakuarhu.
+
+
+ Eranharhikukuaechani kúparhatani
+
+
+ Ampakerani jimpoka %s karanharhikuntaaka chari URLs erakukataecha.
+
+
+ Eranharhikwaechani xanhatani
+
+
+ Eranharhikwaechani xanhatani
+
+
+ + Kúrhuta máteru pékurhintskwa URL
+
+
+ Karakukurhintakuaecha jatakua:
+
+
+ Kuparhatani URL
+
+
+ Kúrhuta máteru pékurhintskwa URL
+
+
+ Kúrhuta máteru pékurhintskwa URL
+
+
+ Miyurakuani karakukurhikuaecharhu kuparhata
+
+
+ Exepakwa ka Eranharhikukwaeri Jatakwa
+
+
+ Jatakwa Erakukwaecha
+
+
+ Pékurhintskwa URLs kéntita
+
+
+ Sánteru míteni
+
+
+ Kúruta ka úra pékurhintskwa karanharhintskua URLs.
+
+
+ URL kúrutani
+
+
+ Mánharita o inchara URL
+
+
+ Éska í: mozilla.org
+
+
+ Éska í: éska í.com
+
+
+ Jimpanhi URL kúparhakukata.
+
+
+ Kéntitani
+
+
+ Kéntitani
+
+
+ Áamku sési karakata jaki URL.
+
+ Wantakwa
+
+ Ánchitakwa
+
+ Jískakata
+ Ayankpekwaechani kwarhukuani
+ Máru eranharhikwaecha inchatiksï ayankperakwaecharu, najkuru áamkuri puchanharhiku
+ Jatakwaeri estakutichani kwarhukuani
+ Í úratarakwa, estakusïnti ampe mánkari eranharhikunka
+ Wantap\'etarakwaechani kwarhukuani
+ Inchanharhikutanhasti jimpoka estakuatikini ka exeratikini máteru úratarakwaechani
+ Jatakwaeri ampe estakutichani kwarhukuani
+ Máru eranharhikwaecha nantarkuni úati énkari ampakeraka
+ Exepakwani kwarhukuani
+
+
+ No diosï meyamua je
+ Materuechaeri estakuechani kuarhukowa
+ Exepakwaechani 3rd kwarhukuani
+
+ Jo
+
+
+ Úratarakwani mítani mítitarantskwani úraparini
+
+
+ Jajkieri mitintskua jimpo mita enkari jurhikutarakua kuparataka o enka eranharhikukua %s xarhanharini jauaka.
+
+
+ Xipatikwa
+
+ Eranharhikwaechani jískani énka úratarakwaechani móptakuaka
+
+ Kwatakwa
+
+ Marhuakwa
+ Kwarhukuani k\'éri xurukwaeri kararakwaechani
+
+ Nántika áamku xarharaa p\'itakataecha
+
+ Kwarhukuni JavaScript
+
+ Eranharhikwaecha kókamintu jatsïranhati, nájkuru nántarkuni jamaka
+
+
+ Íni %1$s jirinhatarantskwani pént\'ani
+
+ Mozilla
+ Jatanhekwa ampe axani
+
+
+ Sánteru mítini
+
+
+ Mozilla jánkurhintasïnti mítini ampe manka sésimintu ánchitaka %1$sm yamintuecha jimpo.
+
+
+ Jískakwa Eyankpekwa
+
+
+ Tsirimerakuaeri ampe
+
+
+ Materu tsirimerariecha
+
+
+ %s |OSS tsirimerariecha
+
+
+ Ankueri %1$s
+
+
+ Jirinhatarantskwaecha jatsïrakurhikataecha
+
+
+ Jirinhatarantskuani erakuni
+
+
+ Jirinhatarantskwaechani pént\'ani
+
+
+ + Jirinhatarantskwa kúparhatani
+ Jirinhatarantskwaechani kéntitani
+ Kéntitani
+
+
+ Jirinhatarantskwa ma kúparhatani
+
+
+ Jirinhatarantskuani erakukurhini:
+
+
+ Jirinhatarantskwa kúparhatani
+
+ Jirinhatarantskwaeri jakakurhikwani jirinhant\'ani
+ Kuturhutapakwani marhuatani
+ Patsani
+
+
+ Éska í: éskaí.com/search/?q=%s
+
+ Jimpanhi jirinhatarantskwa kúparhakukata.
+
+ Jirinhatarantskwaeri jakankurhikwani karanharhitani
+ Jirinhatarantskwa ma mat\'uni jakankuristi.
+
+ Jirinhantskwani karanharhita
+
+ Exeni éska jirinhantskwa sési karakata jaka
+
+
+ Ampakerant\'ani
+
+
+ Kéntitani
+
+
+ Jirinhantskwa ampe kéntitani
+
+
+ %1$s :Erarhukwaecha
+
+
+ Inchaparhakwa kwatakata
+
+
+ Jatsïrakunt\'axati
+
+
+ Eranharhikukwa jatsïrakata
+
+
+ Máteru erakukwaecha
+
+
+ Sánteru ampe
+
+
+ Jirinhapant\'ani
+
+
+ Eranharhikukwani jatsïrant\'ani
+
+
+ Kwanhatsïni
+
+
+ Jatsïrakurhikwani anhaxustani
+
+
+ Máteru úratarakwarhu kw\'anhatsent\'ani
+
+
+ Namuni énka estakutiechani kwarhukuaka
+
+
+ Estakutichani kwarhukuani
+
+ Ch\'éti sésikwaecha
+
+ Máteru úratarakwarhu mítani
+
+ Úakari wéntani %1$s ka íni eranharhikwa mítani %2$s.
+
+ Úratarakwani jirinhant\'ani énka uaka íni mítani
+
+ Áamku ma úratarakwa úa mítani íni eranharhikwa. Úakari wént\'ani %1$s ka jirinhant\'ani %2$s úratarakwa énka úaka.
+
+ Xipatinku Jirinhantskwarhu Wéntari?
+
+
+ %1$s k\'amakukata
+
+
+ Mítani
+
+
+
+
+
+
+
+
+
+ Áamku exentsï marhuatspetini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mikani
+
+
+
+ Maruata je %1$s
+
+
+ Jano je.
+
+
+ Wenakuni
+
+
+
+ %1$s mentaruni jasïsti
+
+
+ Kentitasïnkaksï estakukuaechani enkari mikantanka.
+
+
+
+ Pekurintsï %1$s ka santeruksïni kuajchakuaka enkari nani eranharhikuska.
+
+
+ Ini jirinhatarantskuani pekurintani
+
+
+ Wanajchakuni
+
+
+ Jískakwa ampe winhaskuntsï
+
+ Jirikurhiparini jankwani winhaskukurhintsï. kúparhakwaechani ka máteru jatakwaechani énkaksïni estakuni ka yóntachipanka kwarhukua.
+
+
+ Ch\'éeti jirinhanstkwa, ch\'éeti jánkwa
+
+ Ampema jirinhantaxakiri? Máteru jirinhatarantskwani eraku pékurhintskwaecharhu.
+
+
+ Wenakwarhu jurhikutakwaechani kuparhataa
+
+ Ch\'éeti eranharhikukwaecharhu kw\'anhatsent\'ani %1$s kókamintu. Eraku \"Wenakwarhu kúparhatani\" %1$s jatakwarhu.
+
+
+ Méntku ísï jirikurhiparini
+
+ Péntsï %1$s éska ch\'éeti jirinhatarantskwa ka exeakari éska sánteu sési mítaati eranharhikukwaechani méteru úratarakwaechaeri.
+
+ Ya, parini!
+ Wanach\'akuni
+ Máteru
+
+
+ _
+
+
+ Kúparhatani
+
+
+ JO
+
+
+ Áamkutaru
+
+
+ NO
+
+
+ Jurhikutarakua mitaati najkuru sesimintu kuatakua pakuariaaka
+
+
+ Wénani jirikurhipani jirinhant\'ani
+
+
+ Ayankpekwaecha jinkoni úakari %1$s míantskwani kéntitani clic ma jinkontku. Áamku wétarhinki úratarakwani mítani o exeni ampe manka úni jaka jirinhatarantskwarhu.
+
+
+ Jirinhantskwaeri míantskwa kéntitani
+
+
+ Firefox jatsïrakurhini
+
+
+
+
+
+
+
+
+ úkata jarasti Mozillaeri jinkoni ka máterueri úratarakuaecha.]]>
+
+
+ ixu exentakari.]]>
+
+
+ jinkoni .]]>
+
+
+ GNU mítikata jaka 3, ka úakari exeni ixu .]]>
+
+
+ Jakankurhikwa
+ Mítintskwa
+ Ampakerani
+
+
+
+ Kwatakata inchaparhakwa
+ No kwatakata inchaparhakwa
+
+ %1$s :jimpo exekata
+
+
+ Eranharhikwaeri Kwatakwa
+ URL jarasti ya
+
+
+ Eranharhikwarhu Exent\'ani
+
+
+ Eranharhikukwarhu exent\'ani
+
+
+ %1$d/%2$d
+
+ %1$d ankuri %2$d
+
+
+ Máteru exentskata
+
+ Máteru jirinhantskwani exentsï
+
+ Mirikurhi jirinhantskwani
+
+
+
+
+ Eranharhikwaeri kurhak\'urhikwa
+
+
+ Wenakuarhu
+
+
+ URL p\'itakata
+
+
+ Kwerari ánchitakwaecha
+
+
+ Miyurakuechani mitani
+
+
+ Jorenati
+
+
+ Eranharhikukuaeri sesikuecha
+
+
+ Exepakueri ayankperakua Tsukunhani
+
+
+ Étskuni
+
+
+ Pátani
+
+
+ Exepakueri ayankperakua Tsukunhani
+
+
+ Namunitu eyankperakuechani ixeaka, enkari estakuechani kuarhukuaka, enka ukuriaka.
+
+ -->
+ Exepakueri ayankperakua Tsukunhani
+
+
+ ETSKUKATA ini eranharhikukuarhu
+
+
+ Ixu aamkuksïni ua jarhotani
+
+
+ PATAKATA ini eranharhikukuarhu
+
+
+ Exepakueri ayankperakua Tsukunhani
+
+
+ PATAKATA ini eranharhikukuarhu
+
+
+ ETSKUKATA ini eranharhikukuarhu
+
+
+ Etskuntaari exepakuaeri ayankperata %1$s jimpo?
+
+
+ Patari exepakuaeri ayankperata %1$s jimpo?
+
+
+ Áamkutaru
+
+
+ perakurhikuecha
+
+
+ Kustakurhikua
+
+
+ Jiankuni:
+
+
+ 1. Android pekurhintskuecha
+
+
+ jiankukuecha]]>
+
+
+ Pékurhintskuarhu niarani
+
+
+ %1$s ETSKUNI]]>
+
+
+ Pitani
+
+
+ Arhinhatarakua
+
+
+ Nani jaraski
+
+
+ Eyankperakua
+
+
+ DRM-jimpo xanhatakata
+
+
+ Sesikua kurhajkurhini
+
+
+ Kuarhukukata
+
+
+ Jiankukata
+
+
+ Android jimpo kuarhukukata
+
+
+ Jiankuni eska kustaaka
+
+
+ Kustakuanijku jiankuni
+
+
+ Mintsinharikua
+
+
+ Kustakua kuarhukuni
+
+
+ Jurenkurikua
+
+
+ Menhechani firefox jatsïraati jurenkuarikua ampe.
+
+
+ Sánteru mítini
+
+
+ Uratarakua mikurintaati jimpoka jimpankikurintaati
+
+
+ Kéntitani
+
+
+ Etskukata
+
+
+ Jayarakata
+
+
+ USB/Wi-Fi weratini ampakerant\'ani
+
+
+ Mitakata
+
+
+ Cheeti mitintskua jinkoni jiankuni
+
+
+ Cheeti mitintskua jinkoni uakari utasï maruatani jarani.
+
+
+ Miyurakuani mitani jumpanki eranharhikukuarhu
+
+
+ Mítitarantskwaeri p\'ítakata
+
+
+ Áamkuri mítinhantsï. Tsek\'utantsï.
+
+
+ Manak\'uarhaskari. Tsek\'utantsï.
+
+
+ Ari jirinhantskuecha xarhatani?
+
+
+ Áamku
+
+
+ Jo
+
+
+ Máru jirinhatarantskwaecha áamkusï exerpinki arhistsïkwaechani.
+
+
+ Kéntitani
+
+
+
+
+ Eranharhikwa nántarkuni úxaki?\nTsek\'u Kwatakwa Estakwaeri p\'atani
+
+
+ Yámu miyukwaechani mítani %1$s rhu\n %1$s Pékurhintsï
+
+
+ Miyukwani jimpanhi eranharhikwarhu mítani \n Nákintaru ma miyukwani puchanharhiku
+
+
+ Mitakurhisti jimpanhi eranharhikwa
+
+
+ Móptakuni
+
+
+ Móptakuni jimpanhi eranharhikwarhu
+
+
+ Kéntitani
+
+ Yámu eranharhikukwaechani kéntitaani
+
+
+ Eranharhikwa xerekurhisti
+
+ Éska xipatikwarhu jamani, jucha áamku patsoo o pentaaka eranharhikwani.
+
+ Eranharhikwani míkani
+
+
+
+
+
+ Mozillani ayankuni íni xerekurhikwa
+
+
+
+
+ Estakukuecha kuarhukukata jarastiksï %s jimpo
+
+ Jatakata
+
+ Ayankperakuecha
+
+ Wanhacharhu
+
+ Erakuni
+
+ Sesimintu kuatakua
+
+ PATANHASTI kuatakuechani ini eranharhikukuarhu
+
+ ETSKUNHASTI kuatakuechani ini eranharhikukuarhu
+
+ Kuatakata inchaparhatanhasti
+
+ Aamku kuatakata inchaparhatanhasti
+
+
+ Estakuecha ka maruatatarakuechani kuarhukuni
+
+
+ Kuanhatsïntani
+
+
+
+ Kéntitani
+
+ Jakankuntani
+
+ Jakankuntani
+
+ Jurhikutarakuani jakani
+
+
+ Patsakurhikataecha <b>áamku kéntitanhaa</b> énkari miatskuani %1$s kéntitaka
+
+
+
+ Monharhitani
+
+ Niranti
+
+
+ Turhiperani
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-tt/strings.xml b/mobile/android/focus-android/app/src/main/res/values-tt/strings.xml
new file mode 100644
index 0000000000..dc4d90ce55
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-tt/strings.xml
@@ -0,0 +1,1103 @@
+
+
+
+
+
+
+
+
+ Баш тарту
+
+ ОК
+
+ Саклау
+
+
+ Эзләү яки адрес язу
+
+ Автоматик рәвештә хосусый гизү.\nГизегез. Бетерегез. Кабатлагыз.
+
+
+ Гизү тарихыгыз бетерелде.
+ Гизү тарихы чистартылды
+
+
+ Табның гизү тарихы бетерелде.
+
+
+ %1$s дип эзләү
+
+
+ Уртаклашу…
+
+
+ Сайттагы проблема турында хәбәр итү
+
+
+ %1$s белән ачу
+
+
+ … белән ачу
+
+
+ Өй экранына кую
+
+
+ Ярлыкларга өстәү
+
+ Ярлыклардан алып ату
+
+
+ Көйләүләр
+ Хакында
+ Ярдәм
+ Хокукларыгыз
+
+
+ Күзәтүчеләр блокланды
+
+
+ Бу функцияне сүндерү кайбер сайт проблемаларын төзәтергә мөмкин
+
+
+ Эчтәлекне блоклау
+
+
+ Кайбер сайтларны төзәтү өчен сүндерү
+
+
+ %1$s нигезендә
+
+
+ Аша уртаклашу
+
+ Гизү тарихы бетерелсенме?
+ Гизү тарихыгызны хәвефсез рәвештә бетерү өчен бу искәртүгә басыгыз яки аны чистартыгыз.
+
+
+ Гизү тарихыгызны хәвефсез рәвештә бетерү өчен бу искәртүгә басыгыз яки аны кырыйга сөйрәгез.
+
+ Гизү тарихын бетерү
+
+
+ Ачу
+
+
+ Бетерү һәм ачу
+
+
+ Бетерү
+
+
+ Гизү тарихын бетерү
+
+
+
+ Чистарту һәм ачу
+
+
+ Гизүт тарихын чистартып, %1$s программасын ачу
+
+
+
+ Focus эченнән эзләү
+
+ Klar эченнән эзләү
+
+ Focus Beta эченнән эзләү
+
+ Focus Nightly эченнән эзләү
+
+
+ %1$s дилбегәне Сезнең кулга тапшыра.
+Аны хосусый браузер буларак кулланып, түбәндәгеләрне эшләп була:
+
+ Турыдан-туры кушымта эчендә эзләү һәм пәрәвездә гизү
+ Күзәтүче-трекерларны блоклау (яисә, көйләүләрне яңартып, аларны рөхсәт итү)
+ Кукиларны, эзләү һәм гизү тарихларын җиңел генә бетереп ату
+
+
+%1$s Mozilla тарафыннан җитештерелә. Безнең миссиябез — сәламәт һәм ачык Интернетны үстерү.
+Күбрәк белү
]]>
+
+
+ Хосусыйлык & Хәвефсезлек
+
+
+ Күзәтүчеләр, кукилар, бирелгәннәрне сайлау
+
+
+ Төп итү, автотөгәлләү
+
+
+
+
+ %1$s турында, ярдәм
+
+
+ Күзәтелүдән көчәйтелгән саклау
+
+
+ Web-эчтәлек
+
+
+ Кушымталарны алыштыру
+
+
+ Гомуми
+
+
+ Төп браузер, тел
+
+
+ Мәгълүмат туплау һәм аны куллану
+
+ Эзләү
+
+
+ Эзләү тәкъдимнәрен алу
+
+ %1$s Сез адрес юлында язганны Сезнең эзләү системасына җибәрәчәк
+
+
+ Төп
+
+
+ Эзләү системасы
+
+
+ Кабызылган
+
+
+ Сүнгән
+
+
+ URL-адресны автотөгәлләү
+
+
+ Төп сайтлар өчен
+
+
+ 450-дән артык мәшһүр сайт өчен %s программасының адрес юлында автотөгәлләүне кабызу.
+
+
+ Үзегез өстәгән сайтлар өчен
+
+
+ %s программасында Сез еш кулланган адреслар өчен автотөгәлләүне кабызу.
+
+
+ Сайтлар белән идарә итү
+
+
+ Сайтлар белән идарә итү
+
+
+ + Үзгә URL өстәү
+
+
+ Автотөгәлләү исемлегегез:
+
+
+ URL өстәү
+
+
+ Үзгә URL өстәү
+
+
+ Үзгә URL өстәү
+
+
+ Сылтаманы автотөгәлләнүчеләр исемлегенә өстәү
+
+
+ Кукилар һәм сайт бирелгәннәре
+
+
+ Бирелгәннәрне сайлау
+
+
+ Үзгә URL\'ларны бетерү
+
+
+ Күбрәк өйрәнү
+
+
+ Автотөгәлләү өчен кулланучы үзе сайлаган URL\'ларны өстәү яки алар белән идарә итү.
+
+
+ Өстәләчәк URL
+
+
+ URL\'ны ябыштырыгыз яки языгыз
+
+
+ Мисал: mozilla.org
+
+
+ Мисал: example.com
+
+
+ Яңа үзгә URL өстәлде.
+
+
+ Бетерү
+
+
+ Бетерү
+
+
+ Кертелгән URL\'ны тагын бер кат тикшерегез.
+
+ Тел
+
+ Система теле
+
+ Хосусыйлык
+ Реклама күзәтүчеләрен блоклау
+ Кайбер рекламалар, Сез аларга басмасагыз да, сайтка керүләрегезне күзәтеп тора
+ Аналитика күзәтүчеләрен блоклау
+ Басу һәм скроллинг кебек хәрәкәтләрне җыю, анализлау һәм үлчәү өчен кулланылалар
+ Социаль челтәр күзәтүчеләрен блоклау
+ Сез зиярат иткән сайтларны күзәтү һәм «Уртаклашу» төймәләре кебек мөмкинлекләрне күрсәтү өчен сайтларга куелалар
+ Башка эчтәлек күзәтүчеләрен блоклау
+ Моны кабызу кайбер сәхифәләрнең көтелгәнчә эшләмәвенә сәбәп була ала
+ Кукиларны блоклау
+
+
+ Юк, рәхмәт
+ Өченче тараф трекер кукиларын гына блоклау
+ Өченче тараф кукиларын гына блоклау
+
+ Сайт-ара кукиларны блоклау
+ Әйе, зинһар
+
+
+ Кушымтаны ачу өчен бармак эзен куллану
+
+
+ Ярлыклар өстәгән булсагыз яки вебсайт %s браузерында инде ачылган булса, бармак эзен кулланып ачыгыз.
+
+
+ Күренмәслек
+
+ Кушымталарны алмаштырганда веб-сәхифәләрне яшерү.
+
+ Хәвефсезлек
+
+ Җитештерүчәнлек
+ Веб-шрифтларны блоклау
+
+ Билге һәм рәсемнәрнең күренмәвенә сәбәп була ала
+
+ JavaScript\'ны блоклау
+
+ Сәхифәләр тизрәк йөкләнеләчәк, әмма алар көтелгәнчә эшләмәскә дә мөмкин
+
+
+ %1$s\'ны төп гизгеч итү
+
+ Mozilla
+ Куллану мәгълүматларын җибәрү
+
+
+ Күбрәк белү
+
+
+ Mozilla, %1$s\'ны һәркемгә дә тәкъдим итү һәм яхшырту өчен кирәкле мәгълүматларны гына җыярга тырыша.
+
+
+ Хосусыйлык сәясәте
+
+
+ Лицензия турында мәгълүмат
+
+
+ Без кулланган китапханәләр
+
+
+ %s | OSS китапханәләр
+
+
+ %1$s турында
+
+
+ Урнаштырылган эзләү системалары
+
+
+ Эзләү системасын сайлау
+
+
+ Төп эзләү системаларын яңадан торгызу
+
+
+ + Башка бер эзләү системасын өстәү
+ Эзләү системаларын бетерү
+ Бетерү
+
+
+ Башка бер эзләү системасын өстәү
+
+ Үзегез теләгән эзләү системасын сайлагыз:
+
+
+ Эзләү системасын өстәү
+
+ Эзләү системасының исеме
+ Эзләнәчәк сүз я сүзтезмә
+ Саклау
+
+
+ Мисал: example.com/search/?q=%s
+
+ Яңа эзләү системасы өстәлде.
+
+ Эзләү системасының исемен кертегез
+ Әлеге исем урнаштырылган бер эзләү системасы тарафыннан кулланыла инде.
+
+ Эзләнәсе сүзтезмәне кертегез
+
+ Эзләнәсе сүзтезмәнең Мисал форматына туры килүен тикшерегез
+
+
+ Кертелгәнне чистарту
+
+
+ Ябу
+
+
+ Гизү тарихын бетерү
+
+
+ Ачык таблар: %1$s
+
+
+ Хәвефсез бәйләнеш
+
+
+ Йөкләнелә
+
+
+ Веб-сайт йөкләнде
+
+
+ Күбрәк опцияләр
+
+
+ Күбрәк опцияләр төймәсе
+
+
+ Алга бару
+
+
+ Сайтны яңадан йөкләү
+
+
+ Кире кайту
+
+
+ Веб-сайтны йөкләүне туктату
+
+
+ Соңгы кушымтага кире кайту
+
+
+ Блокланган күзәтүчеләр саны
+
+
+ Күзәтүчеләрне блоклау
+
+ Хокукларыгыз
+
+ Сылтаманы башка кушымтада ачу
+
+ Бу сылтаманыі %2$s ярдәме белән ачу өчен, сез %1$s\'тан чыга аласыз.
+
+ Сылтаманы ача алучы кушымта табу
+
+ Җиһазыгыздагы бер кушымта да бу сылтаманы ача алмый. Аны ача ала алучы кушымтаны %2$s эченнән эзләү өчен, Сез %1$s\'тан чыга аласыз.
+
+ Хосусый гизү режимыннан чыгаргамы?
+
+
+ %1$s йөкләнде
+
+
+ Ачу
+
+
+
+
+
+
+
+
+
+
+ Ярлыкларга өстәлде!
+
+ Сервер табылмады
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ябу
+
+
+
+ %1$s кушымтасына рәхим итегез
+
+
+ Тиз. Хосусый. Бүлдерүләрсез.
+
+
+ Башлау
+
+
+
+ %1$s башка браузерлар кебек түгел
+
+
+ Өстәмә хосусыйлык өчен кушымтаны япканда, без тарихыгызны чистартабыз.
+
+
+
+ Һәр сылтаманы ачканда да мәгълүматыгыз саклансын өчен %1$s браузерын төп браузер итеп билгеләгез.
+
+
+ Төп браузер итеп билгеләү
+
+
+ Калдырып тору
+
+
+ Хосусыйлыгызны көчәйтегез
+
+ Хосусый гизүне яңа дәрәҗәгә күтәрегез. Сайтлар аша Сезне күзәтүче һәм сәхифәләрнең йөкләнү вакытын арттыручы рекламаларны һәм башка эчтәлекләрне блоклагыз.
+
+
+ Сезнең эзләү, Сезнең кагыйдәләр
+
+ Башка берәр нәрсә эзлисезме? Көйләүләр менюсына кереп, төп итеп башка бер эзләү системасын сайлый аласыз.
+
+
+ Өй экранына ярлыклар өстәгез
+
+ %1$s\'та иң яраткан сайтларыгызга тиз генә кире кайта аласыз. Моның өчен %1$s менюсыннан \"Өй экранына кушуны\" сайлау җитә.
+
+
+ Хосусыйлыкны гадәткә кертү
+
+ %1$s\'ны төп гизгечегез итеп сайлап, башка кушымталардан сайтларны ачканда да хосусый гизү мөмкинлегеннән файда күрегез.
+
+ Яхшы, аңладым!
+ Кичү
+ Киләсе
+
+
+ -
+
+
+ Өстәү
+
+
+ ӘЙЕ
+
+
+ Баш тарту
+
+
+ ЮК
+
+
+ Ярлык «Күзәтелүдән көчәйтелгән саклау» режимы сүндерелгән килеш ачылачак
+
+
+ Хосусый гизү утырышы
+
+
+ Бердерүләр аша %1$s утырышыгызны бер орыну белән бетерә аласыз. Кушымтаны ачу яисә гизгечтә нәрсә ачык икәнен күрү кирәк түгел.
+
+
+ Гизү тарихын бетерү
+
+
+ Firefox\'ны йөкләп алу
+
+
+
+
+
+
+
+
+ Mozilla Public License һәм башка ачык чыганак лицензияләренең шартлары белән тәкъдим ителә.]]>
+
+
+ монда табып була.]]>
+
+
+ лицензияләләре астында нәшер ителә.]]>
+
+
+ GNU General Public License v3 шартлары белән һәм түләүсез монда табып була.]]>
+
+
+ Кулланучы исеме
+ Серсүз
+ Чистарту
+
+
+
+ Хәвефсез бәйләнеш
+ Хәвефсез булмаган бәйләнеш
+
+ Раслаучы: %1$s
+
+
+ Сайт хәвефсезлеге
+ URL инде бар
+
+
+ Биттә табу
+
+
+ Биттә табу
+
+
+ %1$d/%2$d
+
+ %2$d дан %1$d
+
+
+ Алдагы нәтиҗәне табу
+
+ Арттагы нәтиҗәне табу
+
+ Биттә эзләүне тәмамлау
+
+
+
+
+ Сайтның тулы версиясен сорау
+
+
+ Сайтның компьютер версиясе
+
+
+ URL копияләнде
+
+
+ Җитештерүче кораллары
+
+
+ Сылтамаларны кушымталарда ачу
+
+
+ Киңәйтелгән
+
+
+ Сайт рөхсәтләре
+
+
+ Cookie баннерларын киметү
+
+
+ Кабынган
+
+
+ Сүнгән
+
+
+ Cookie баннерларын киметү
+
+
+ Мөмкинлек булганда cookie сорауларын автоматик рәвештә кире кагып, баннерларны азрак күрегез.
+
+ -->
+ Cookie баннерларын киметү
+
+
+ Бу сайт өчен кабынган
+
+
+ Бу сайт хәзерге вакытта тәэмин ителми
+
+
+ Бу сайт өчен cүнгән
+
+
+ Cookie баннерларын киметү
+
+
+ Бу сайт өчен cүнгән
+
+
+ Бу сайт өчен кабынган
+
+
+ %1$s өчен cookie баннерлары киметелсенме?
+
+
+ %1$s өчен cookie баннерлары киметелмәсенме?
+
+
+ %1$s бу сайтның cookie файлларын чистартып, битне яңартачак. Барлык cookie-ларны да чистарту сайттан чыгуга яки сайттагы алыш-биреш кәрзинегезнең бушавына китерә ала.
+
+
+ %1$s cookie сорауларын автоматик рәвештә кире кагарга тырышып карый ала.
+
+
+ Бу сайт хәзерге вакытта Cookie баннерларын киметү белән тәэмин ителми. Төркемебезнең бу сайтны карап чыгып, киләчәктә тәэмин ителүен теләр идегезме?
+
+
+ Баш тарту
+
+
+ Тәэмин ителүен сорау
+
+
+ Бу сайтның тәэмин ителүен сорау җибәрелде.
+
+
+ Бу сайтның тәэмин ителүен сорау җибәрелде.
+
+
+
+ %1$s, туйдырып бетерүчу куки баннерларын яшерү өчен куки үтенечләрен кире кагарга тырыша.\n\nКуки баннерларына кагылышлы опцияләрне %2$s үзгәртә аласыз.
+
+ көйләүләр
+
+
+ Автоуйнату
+
+
+ Рөхсәт итү өчен:
+
+
+ 1. Android көйләүләренә керегез
+
+
+ Рөхсәтләргә басыгыз]]>
+
+
+ Көйләүләргә күчү
+
+
+ %1$s көйләвен кабызыгыз]]>
+
+
+ Камера
+
+
+ Микрофон
+
+
+ Урнашу
+
+
+ Искәртү
+
+
+ DRM белән идарә ителгән эчтәлек
+
+
+ Рөхсәт соралсын
+
+
+ Тыелган
+
+
+ Рөхсәт ителгән
+
+
+ Android тарафыннан тыелган
+
+
+ Аудио һәм видеоны рөхсәт итү
+
+
+ Аудионы гына тыю
+
+
+ Киңәш ителә
+
+
+ Аудио һәм видеоны тыю
+
+
+ Тикшеренүләр
+
+
+ Firefox кайвакытта тикшеренүләр урнаштырырга һәм үткәрергә мөмкин.
+
+
+ Күбрәк белү
+
+
+ Үзгәртүләрне гамәлгә ашыру өчен кушымта ябылачак
+
+
+ Бетерү
+
+
+ Актив
+
+
+ Тәмамланды
+
+
+ USB/Wi-Fi аркылы хаталарны ерактан төзәтү
+
+
+ Ачу
+
+
+ Бармак эзегезне кулланып раслау
+
+
+ Хәзерге кушымта сессиясен дәвам итү өчен бармак эзегезне куллана аласыз.
+
+
+ Сылтаманы яңа утырышта ачу
+
+
+ Бармак эзе рәсеме
+
+
+ Бармак эзе танылмады. Тагын бер кат тырышып карагыз.
+
+
+ Бармак артык тиз алынды. Тагын бер кат тырышып карагыз.
+
+
+ Эзләү тәкъдимнәре күрсәтелсенме?
+
+
+ Тәкъдимнәрен алыр өчен, %1$s Сез адрес юлында язганнарны эзләү системасына җибәрергә тиеш.
+
+
+ Юк
+
+
+ Әйе
+
+
+ Кайбер эзләү машиналары тәкъдимнәр ясамый.
+
+
+ Яшерү
+
+
+
+
+ Сайт көтелгәнчә эшләмиме?\nКүзәтүчеләрдән саклауны сүндереп карагыз
+
+
+ Өй экранга өстәү]]>
+
+
+ Һәр сылтаманы да %1$s\'та ачу\n%1$s\'ны төп гизгеч итегез
+
+
+ Иң еш кулланылган сайтларның адресларын автотөгәлләү өчен\nАдрес юлындагы URL\'га озак итеп басып торыгыз
+
+
+ Сылтаманы яңа биттә ачу өчен\nСылтамага озак итеп басып торыгыз
+
+
+ Өй экрандагы киңәшләрне сүндерү
+
+
+ Яңа таб ачылды
+
+
+ Күчү
+
+
+ Тулы экран режимына күчү
+
+
+ Яңа табтагы сылтамага шундук күчү
+
+
+ Куркыныч булырга мөмкин һәм ялган сайтларны блоклау
+
+ Шикаять ителгән ялган һәм Һөҗүмче сайтларны, зарарлы һәм кирәкмәс программалы сайтларны блоклау.
+
+
+ Тик-HTTPS режимы
+
+
+ Хәвефсезлекне арттыру өчен сайтларга автоматик рәвештә HTTPS шифрлау протоколын кулланып тоташырга тырыша.
+
+
+ Чыгармалар
+
+ Бу вебсайтлар өчен эчтәлекне блоклауны сүндердегез.
+
+ Бетерү
+
+ Барлык вебсайтларны да бетерү
+
+
+ Кукиларны блоклау
+
+
+ Кукиларны блокларга телисезме?
+
+
+ Таб ватылды
+
+ Гафу итегез, әмма бу таб белән бер проблема бар.
+
+
+ Хосусый браузер буларак, без бу табны беркайчан да сакламыйбыз һәм яңадан торгыза алмыйбыз.
+
+ Табны ябу
+
+
+ Mozilla-га ватылу турында хәбәр җибәрү
+
+
+
+
+ %s көненнән башлап блокланган күзәтүчеләр
+
+ Эчтәлек
+
+ Реклама
+
+ Социаль челтәрләр
+
+ Аналитика
+
+ Күзәтелүдән Көчәйтелгән Саклау
+
+ Бу сайт өчен саклау СҮНГӘН
+
+ Бу сайт өчен саклау КАБЫНГАН
+
+ Бәйләнеш хәвефсез
+
+ Бәйләнеш хәвефсез түгел
+
+ Блоклау өчен трекерлар һәм скриптлар
+
+
+ Кире кайту
+
+
+
+ Бетерү
+
+
+ Исемен үзгәртү
+
+ Исемен үзгәртү
+
+
+ Ярлык исеме
+
+
+ %1$s тарихын бетергәндә, сакланган яки уртаклашылган рәсемнәр <b>бетерелмәячәк</b>
+
+
+
+ Тема
+
+ Ачык
+
+ Караңгы
+
+ Батареяны саклау режимын исәпкә алу
+
+ Җиһаз темасына иярү
+
+
+
+ Бу сайт HTTPS кулланмый
+
+
+ Күбрәк белү
+ Бу көйләнүне үзгәртү өчен Көйләүләр > Хосусыйлык һәм Хәвефсезлек > Хәвефсезлек бүлегенә күчегез.]]>
+
+
+ Бәйләнеш хәвефсез түгел
+
+
+
+ Моңа кадәр бу серверга уңышлы тоташа торган булсагыз, бу хата вакытлыча гына булырга мөмкин.
+ ]]>
+
+
+ Кемдер үзенең сайтын бу сайт дип күрсәтергә тырыша кебек, шунлыктан дәвам итү куркыныч булырга мөмкин.
+
+ %1$s кушымтасы %2$s сайтына ышанмый, чөнки сертификатын бастыручу я билгесез, я ул үзлегеннән имзаланган, я сервер дөрес арадашчы сертификатлар җибәрми.
+ ]]>
+
+
+
+ Табны ябу
+
+
+
+ Тотылдылар! Без бу сайтны Сезне күзәтүдән тыйдык. Ниләр блоклаганганын һәрвакыт калканга басып күреп була.
+
+
+ Калыкмә тәрәзәне ябу
+
+
+
+ Сез саклангансыз!
+
+ Бу стандарт көйләүләр көчле саклану тәэмин итә. Әмма ихтыяҗларыгыз башкачарак булса, аларны үзгәртү җиңел.
+
+ Ябу
+
+
+ Тарих, сookie файллары, аслында һәрнәрсәне дә бетереп, яңа табта яңа тормыш башлар өчен монда басыгыз.
+
+
+
+
+ Ябу
+
+
+ Эзләү виджеты
+
+
+ Гизү тарихы чистартылды! 🎉
+
+
+ Хосусый гизү сеансын башлап җибәрегез, без исә трекерларны һәм башка начар нәрсәләрне блокларбыз.
+
+
+ Хосусый режимда дәвам итәрсез, әмма %1$s виджетын Өй экранына куйсагыз, киләчәктә хосусый режимга тизрәк керә алачаксыз.
+
+
+ Виджетны өй экранына өстәү
+
+
+ Виджет өй экранына өстәлде
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-uk/strings.xml b/mobile/android/focus-android/app/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000000..8f6045a38a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-uk/strings.xml
@@ -0,0 +1,1120 @@
+
+
+
+
+
+
+
+
+ Скасувати
+
+ OK
+
+ Зберегти
+
+
+ Введіть запит чи адресу
+
+ Автоматичний приватний перегляд.\nПереглядайте. Видаляйте. Повторюйте.
+
+
+ Ваша історія перегляду була очищена.
+ Історію перегляду очищено
+
+
+ Історію перегляду було стерто.
+
+
+ Шукати %1$s
+
+
+ Поділитися…
+
+
+ Повідомити про сайт
+
+
+ Відкрити в %1$s
+
+
+ Відкрити в…
+
+
+ Додати на головний екран
+
+
+ Додати до ярликів
+
+ Вилучити з ярликів
+
+
+ Налаштування
+ Про
+ Довідка
+ Ваші права
+
+
+ Заблоковано стеження
+
+
+ Вимкнення цієї функції може вирішити деякі проблеми з сайтом
+
+
+ Блокування вмісту
+
+ Вимкнути для усунення проблем з деякими сайтами
+
+
+ За підтримки %1$s
+
+
+ Поділитись через
+
+ Стерти історію перегляду?
+ Торкніться або приберіть це сповіщення, щоб надійно стерти свою історію перегляду.
+
+
+ Торкніться або посуньте це сповіщення, щоб надійно стерти свою історію перегляду.
+
+ Стерти історію перегляду
+
+
+ Відкрити
+
+
+ Стерти і відкрити
+
+
+ Стерти
+
+
+ Стерти історію перегляду
+
+
+
+ Стерти і відкрити
+
+
+ Стерти і відкрити %1$s
+
+
+
+ Пошук у Focus
+
+ Пошук у Klar
+
+ Пошук у Focus Beta
+
+ Пошук у Focus Nightly
+
+
+ %1$s надає вам контроль.
+Використовуйте його як приватний браузер:
+
+ Шукайте і переглядайте безпосередньо в програмі
+ Блокуйте стеження (або змініть налаштування, щоб дозволити його)
+ Видаляйте файли cookie, а також історію пошуку та перегляду
+
+
+%1$s створено в Mozilla. Наша місія – розвивати здоровий та відкритий Інтернет.
+Докладніше
]]>
+
+
+ Приватність і безпека
+
+
+ Стеження, файли cookie, вибір даних
+
+
+ Встановити типовим, автозавершення
+
+
+
+
+ Про %1$s, довідка
+
+
+ Розширений захист від стеження
+
+
+ Вебвміст
+
+
+ Перемикання програм
+
+
+ Загальні
+
+
+ Типовий браузер, мова
+
+
+ Збір даних і використання
+
+ Пошук
+
+
+ Отримувати пошукові пропозиції
+
+ %1$s відправлятиме пошуковій системі текст, введений в адресному рядку
+
+
+ Типова
+
+
+ Пошукова система
+
+
+ Увімкнено
+
+
+ Вимкнено
+
+
+ Автозавершення URL
+
+
+ Для популярних сайтів
+
+
+ Увімкніть автозавершення в %s для понад 450 популярних URL-адрес у панелі адреси.
+
+
+ Для доданих вами сайтів
+
+
+ Увімкнути, щоб %s автоматично завершував URL-адреси популярних сайтів.
+
+
+ Керувати сайтами
+
+
+ Керувати сайтами
+
+
+ + Додати власну URL-адресу
+
+
+ Ваш список автозаповнення:
+
+
+ Додати URL
+
+
+ Додати власну URL-адресу
+
+
+ Додати власну URL-адресу
+
+
+ Додати посилання для автозавершення
+
+
+ Файли cookie та дані сайтів
+
+
+ Вибір даних
+
+
+ Вилучити власні URL-адреси
+
+
+ Докладніше
+
+
+ Додавайте і керуйте автозавершенням власних URL-адрес.
+
+
+ URL для додавання
+
+
+ Вставте чи введіть URL-адресу
+
+
+ Зразок: mozilla.org
+
+
+ Зразок: example.com
+
+
+ Нову власну URL-адресу додано.
+
+
+ Вилучити
+
+
+ Вилучити
+
+
+ Уважно перевірте введену URL-адресу.
+
+ Мова
+
+ Типова системна
+
+ Приватність
+ Блокувати рекламу зі стеженням
+ Деяка реклама відстежує відвідування сайтів, навіть якщо ви на неї не натискали
+ Блокувати аналітику зі стеженням
+ Використовуються для збору, аналізу та вимірювання активності, наприклад, дотики і прокручування
+ Блокувати стеження соціальних мереж
+ Вбудовуються в сайти для відстеження ваших відвідувань та показу додаткових функцій, наприклад, кнопки \"Поділитися\"
+ Блокувати інші елементи стеження
+ Увімкнення цієї функції може викликати непередбачувану поведінку деяких сторінок
+ Блокувати файли cookie
+
+
+ Ні, дякую
+ Блокувати лише сторонні файли cookie стеження
+ Блокувати лише сторонні файли cookie
+
+ Блокувати міжсайтові файли cookie
+ Так, будь ласка
+
+
+ Використовувати відбиток пальця для розблокування
+
+
+ Розблокуйте відбитком пальця, якщо ви додали ярлики, або якщо вебсайт вже відкритий у %s.
+
+
+ Під прикриттям
+
+ Приховувати вебсторінки під час перемикання застосунків та не дозволяти знімки екрана.
+
+ Безпека
+
+ Швидкодія
+ Блокувати вебшрифти
+
+ Може призвести до втрати піктограм чи зображень
+
+ Блокувати JavaScript
+
+ Сторінки можуть завантажуватись швидше, але при цьому можуть неправильно працювати
+
+
+ Зробити %1$s типовим браузером
+
+ Mozilla
+ Надсилати дані про використання
+
+
+ Докладніше
+
+
+ Mozilla намагається збирати лише дані, що необхідні для роботи і вдосконалення %1$s для кожного.
+
+
+ Положення про приватність
+
+
+ Інформація про ліцензію
+
+
+ Бібліотеки, які ми використовуємо
+
+
+ %s | Вільні бібліотеки
+
+
+ Про %1$s
+
+
+ Встановлені пошукові системи
+
+
+ Виберіть пошукову систему
+
+
+ Відновити типові пошукові системи
+
+
+ + Додати іншу пошукову систему
+ Вилучити пошукові системи
+ Вилучити
+
+
+ Додати іншу пошукову систему
+
+ Виберіть бажану пошукову систему:
+
+
+ Додати пошукову систему
+
+ Назва пошукової системи
+ Запит для пошуку
+ Зберегти
+
+
+ Зразок: example.com/search/?q=%s
+
+ Нову пошукову систему додано.
+
+ Введіть назву пошукової системи
+ Ця назва вже використовується у встановленій пошуковій системі.
+
+ Введіть пошуковий запит
+
+ Перевірте пошуковий запит на відповідність формату зразка
+
+
+ Очистити поле
+
+
+ Відхилити
+
+
+ Стерти історію перегляду
+
+
+ Відкритих вкладок: %1$s
+
+
+ Безпечне з\'єднання
+
+
+ Завантаження
+
+
+ Вебсайт завантажено
+
+
+ Додатково
+
+
+ Кнопка додаткових налаштувань
+
+
+ Перехід уперед
+
+
+ Перезавантажити вебсайт
+
+
+ Перейти назад
+
+
+ Зупинити завантаження сайту
+
+
+ Повернутись до попередньої програми
+
+
+ Заблоковано стеження
+
+
+ Блокувати стеження
+
+ Ваші права
+
+ Відкрити посилання в іншій програмі
+
+ Ви можете вийти з %1$s, щоб відкрити це посилання в %2$s.
+
+ Знайдіть програму, яка може відкрити посилання
+
+ Жодна з програм на вашому пристрої не здатна відкрити це посилання. Ви можете вийти з %1$s для пошуку відповідної програми у %2$s.
+
+ Вийти з приватного перегляду?
+
+
+ %1$s завантажено
+
+
+ Відкрити
+
+
+
+
+
+
+
+
+
+
+ Додано до ярликів!
+
+ Сервер не знайдено
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Закрити
+
+
+
+ Вітаємо в %1$s
+
+
+ Швидкий. Приватний. Зосереджений.
+
+
+ Розпочати
+
+
+
+ %1$s не схожий на інші браузери
+
+
+ Для кращої приватності ми очищаємо вашу історію, коли ви закриваєте програму.
+
+
+
+ Зробіть %1$s типовим, щоб захистити ваші дані під час відкриття кожного посилання.
+
+
+ Установити типовим браузером
+
+
+ Пропустити
+
+
+
+ Прокачайте вашу приватність
+
+ Перейдіть на вищий рівень приватного перегляду. Блокуйте рекламу та інший вміст, що може стежити за вами на сайтах і сповільнювати завантаження сторінок.
+
+
+ Ваш пошук, ваш шлях
+
+ Шукаєте щось інше? Виберіть іншу пошукову систему в налаштуваннях.
+
+
+ Додавайте ярлики на головний екран
+
+ Швидко відкривайте свої улюблені сайти у %1$s. Просто оберіть \"Додати на головний екран\" в меню %1$s.
+
+
+ Зробіть приватність звичкою
+
+ Встановіть %1$s своїм типовим браузером та отримайте переваги приватного перегляду при переході на вебсайти з інших застосунків.
+
+ Гаразд, зрозуміло!
+ Пропустити
+ Далі
+
+
+ -
+
+
+ Додати
+
+
+ ТАК
+
+
+ Скасувати
+
+
+ НІ
+
+
+ Ярлик відкриватиметься з вимкненим Розширеним захистом від стеження
+
+
+ Сеанс приватного перегляду
+
+
+ Сповіщення дозволяють вам стирати дані сеансу %1$s одним дотиком. Вам не потрібно відкривати програму чи дивитися, що відбувається у вашому браузері.
+
+
+ Стерти історію перегляду
+
+
+ Завантажити Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License та інших ліцензій відкритого коду.]]>
+
+
+ тут.]]>
+
+
+ ліцензій вільного й відкритого програмного забезпечення.]]>
+
+
+ GNU General Public License v3, і доступні тут .]]>
+
+
+ Ім\'я користувача
+ Пароль
+ Очистити
+
+
+
+ Захищене з\'єднання
+ Незахищене з’єднання
+
+ Засвідчено: %1$s
+
+
+ Безпека сайту
+ URL вже наявний
+
+
+ Знайти на сторінці
+
+
+ Знайти на сторінці
+
+
+ %1$d/%2$d
+
+ %1$d з %2$d
+
+
+ Знайти наступний результат
+
+ Знайти попередній результат
+
+ Прибрати пошук на сторінці
+
+
+
+
+ Повна версія сайту
+
+
+ Версія для комп’ютера
+
+
+ URL скопійовано
+
+
+ Інструменти розробника
+
+
+ Відкривати посилання у програмах
+
+
+ Додатково
+
+
+ Дозволи сайтів
+
+
+ Зменшення кількості банерів про файли cookie
+
+
+ Увімкнено
+
+
+ Вимкнено
+
+
+ Зменшення кількості банерів про файли cookie
+
+
+ Переглядайте менше банерів, автоматично відхиляючи запити на збереження файлів cookie, коли це можливо.
+
+ -->
+ Зменшення кількості банерів про файли cookie
+
+
+ УВІМКНЕНО для цього сайту
+
+
+ Сайт наразі не підтримується
+
+
+ ВИМКНЕНО для цього сайту
+
+
+ Зменшення кількості банерів про файли cookie
+
+
+ ВИМКНЕНО для цього сайту
+
+
+ УВІМКНЕНО для цього сайту
+
+
+ Увімкнути зменшення кількості банерів про файли cookie для %1$s?
+
+
+ Вимкнути зменшення кількості банерів про файли cookie для %1$s?
+
+
+ %1$s очистить файли cookie цього сайту та оновить сторінку. Очищення всіх файлів cookie може призвести до виходу з облікових записів або очищення кошика для покупок.
+
+
+ %1$s може намагатися автоматично відхиляти запити на збереження файлів cookie.
+
+
+ Цей сайт наразі не підтримується функцією зменшення кількості банерів про файли cookie. Хочете звернутися до нашої команди, щоб ми розглянули можливість додати підтримку цього вебсайту в майбутньому?
+
+
+ Скасувати
+
+
+ Звернутися по підтримку
+
+
+ Запит на підтримку сайту надіслано.
+
+
+ Запит на підтримку сайту надіслано.
+
+
+
+ %1$s намагається відхиляти запити про збереження файлів cookie, щоб уникнути надокучливих банерів.\n\nКеруйте поведінкою банерів про файли cookie в %2$s.
+
+
+ налаштуваннях
+
+
+ Автовідтворення
+
+
+ Щоб дозволити це:
+
+
+ 1. Відкрийте налаштування Android
+
+
+ Дозволи]]>
+
+
+ Перейти до налаштувань
+
+
+ %1$s]]>
+
+
+ Камера
+
+
+ Мікрофон
+
+
+ Розташування
+
+
+ Сповіщення
+
+
+ Вміст, контрольований DRM
+
+
+ Запитувати дозвіл
+
+
+ Заблоковано
+
+
+ Дозволено
+
+
+ Заблоковано Android
+
+
+ Дозволити аудіо та відео
+
+
+ Блокувати лише аудіо
+
+
+ Рекомендовано
+
+
+ Блокувати аудіо та відео
+
+
+ Дослідження
+
+
+ Firefox може час від часу встановлювати та виконувати дослідження.
+
+
+ Докладніше
+
+
+ Програма закриється, щоб застосувати зміни
+
+
+ Вилучити
+
+
+ Активні
+
+
+ Завершено
+
+
+ Віддалене налагодження через USB/Wi-Fi
+
+
+ Розблокувати
+
+
+ Підтвердьте з відбитком пальця
+
+
+ Ви можете скористатися відбитком пальця, щоб продовжити поточний сеанс програми.
+
+
+ Відкрити посилання в новому сеансі
+
+
+ Піктограма відбитку пальця
+
+
+ Відбиток пальця не розпізнано. Спробуйте знову.
+
+
+ Надто швидкий рух пальцем. Спробуйте знову.
+
+
+ Показувати пошукові пропозиції?
+
+
+ Для отримання пропозицій %1$s повинен відправляти пошуковій системі введений в адресному рядку текст.
+
+
+ Ні
+
+
+ Так
+
+
+ Деякі пошукові системи не можуть показувати пропозиції.
+
+
+ Відхилити
+
+
+
+
+ Неочікувана поведінка сайту?\n Спробуйте вимкнути захист від стеження
+
+
+ Додати на головний екран]]>
+
+
+ Відкривайте всі посилання в %1$s\n Встановіть %1$s типовим браузером
+
+
+ Автозавершення популярних URL-адрес\n Тривалий дотик до URL в адресному рядку
+
+
+ Відкривайте посилання в новій вкладці\n Тривалий дотик до посилання на сторінці
+
+
+ Вимкнути поради у домівці
+
+
+ Відкрито нову вкладку
+
+
+ Перемкнути
+
+
+ Вхід у повноекранний режим
+
+
+ Негайно перемикати на посилання в новій вкладці
+
+
+ Блокувати потенційно небезпечні та підозрілі сайти
+
+ Блокувати відомі підозрілі та зловмисні сайти, шкідливі сайти та сайти з небажаним програмним забезпеченням.
+
+
+ HTTPS-режим
+
+
+ Намагатися автоматично доступатися до сайтів за допомогою протоколу шифрування HTTPS для поліпшення безпеки.
+
+
+ Винятки
+
+ Ви вимкнули блокування вмісту для цих вебсайтів.
+
+ Вилучити
+
+ Вилучити всі вебсайти
+
+
+ Блокувати файли cookie
+
+
+ Бажаєте блокувати файли cookie?
+
+
+ Збій вкладки
+
+ Вибачте. Виникли проблеми з цією вкладкою.
+
+ Так як це приватний браузер, ми ніколи не зберігаємо і не можемо відновити цю вкладку.
+
+ Закрити вкладку
+
+
+
+
+
+ Надіслати звіт про збій в Mozilla
+
+
+
+
+ Заблоковано стеження, починаючи з %s
+
+ Вміст
+
+ Реклама
+
+ Соціальні мережі
+
+ Аналітика
+
+ Розширений захист від стеження
+
+ Захист на цьому сайті вимкнено
+
+ Захист на цьому сайті увімкнено
+
+ Захищене з’єднання
+
+ З’єднання не захищене
+
+ Блокувати стеження й скрипти
+
+
+ Назад
+
+
+
+ Вилучити
+
+
+ Перейменувати
+
+ Перейменувати
+
+
+ Назва ярлика
+
+
+ Збережені та спільні зображення <b>не будуть видалені</b>, якщо ви зітрете історію %1$s
+
+
+
+ Тема
+
+ Світла
+
+ Темна
+
+ Враховувати режим економії заряду
+
+ Тема пристрою
+
+
+
+ Цей сайт не підтримує HTTPS
+
+
+ Докладніше
+ Змініть це налаштування перейшовши до Налаштування > Приватність і безпека > Безпека.]]>
+
+
+ Незахищене зʼєднання
+
+
+
+ Якщо ви раніше отримували доступ до цього сервера, то це може бути тимчасова помилка.
+ ]]>
+
+
+ Хтось може намагатися видати себе за сайт. Подальший перехід може бути ризикованим.
+
+ %1$s не довіряє %2$s , тому що видавець його сертифіката невідомий, сертифікат самопідписаний, або сервер не надсилає правильні посередницькі сертифікати
+ ]]>
+
+
+
+ Закрити вкладку
+
+
+
+ Спіймався! Ми не дозволили цьому сайту стежити за вами. Торкніться щита, щоб побачити заблоковані елементи.
+
+
+ Закрити спливне вікно
+
+
+
+ Ви захищені!
+
+ Ці типові налаштування забезпечують надійний захист. Але їх можна легко змінити відповідно до ваших особистих потреб.
+
+ Відхилити
+
+
+ Торкніться тут, щоб стерти все – історію, файли cookie й інші дані – та розпочніть начисто у новій вкладці.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Закрити
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Віджет пошуку
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Історію перегляду очищено! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Розпочніть сеанс приватного перегляду, а ми заблокуємо стеження та інші небажані елементи.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Ми залишимо вас у режимі приватного перегляду, але наступного разу розпочніть роботу швидше за допомогою віджета %1$s на головному екрані.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Додати віджет на головний екран
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Віджет додано на головний екран
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-ur/strings.xml b/mobile/android/focus-android/app/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000000..61ae5d60fb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-ur/strings.xml
@@ -0,0 +1,818 @@
+
+
+
+
+
+
+
+
+ منسوخ کریں
+
+ ٹھیک ہے
+
+ محفوظ کریں
+
+
+ پتہ تلاش یا داخل کریں
+
+ خودکار نجی برائوزنگ۔\nبرائوز کریں۔ جزف کریں۔ دہرائیں۔
+
+
+ آپ کے برائوزنگ سابقات مٹا دیئے گئے ہیں۔
+
+ براؤزنگ کی سابقات صاف ہو گئی۔
+
+
+ ٹیب کی برائوزنگ سابقات مٹا دی گئی ہیں۔
+
+
+ ۔%1$s کے لئے تلاش کریں
+
+
+ شیئر کریں…
+
+
+ سائٹ مسلہ رپورٹ کریں
+
+
+ %1$s میں کھولیں
+
+
+ …میں کھولیں
+
+
+ ابتدائی اسکرین پر اظافہ کریں
+
+
+ شارٹ کٹ میں شامل کریں
+
+ شارٹ کٹ سے ہٹائیں
+
+ سیٹنگیں
+ کے بارے میں
+ مدد
+ آپ کے حقوق
+
+
+ ٹریکر بلاک کردہ
+
+
+ اسے بند کرنے سے سائٹ کے کچھ مسائل ٹھیک ہو سکتے ہیں۔
+
+
+ مواد کو روکیں
+
+ کچھ سائٹس کو ٹھیک کرنے کے لئے بند کر دیں
+
+
+ ان کے ذریعہ %1$s
+
+
+ کے زریعے شیئر کریں
+
+
+ برائوزنگ سابقات مٹائیں
+
+
+ کھولیں
+
+
+ مٹائیں اور کھولیں
+
+
+ مٹائيں
+
+
+ برائوزنگ سابقات مٹائیں
+
+
+
+ مٹائیں اور کھولیں
+
+
+ مٹائیں اور %1$s کھولیں
+
+
+
+ %1$s آپ کو کنٹرول دیتی ہے
+ اسکو نجی برائوزر کی طرح استعمال کریں:
+
+ اپلی کیشن میں ہی تلاش اور براؤز کریں
+ ٹریکررٰ کو بلاک کریں (یا سٹینگوں کی تازہ کاری کر کے ٹریکررٰ کو اجازت دیں)۔
+ کوکیز کے ساتھ تلاش اور براؤزنگ سابقات کو بھی حذف کرنے کیلیے مٹائیں
+
+
+%1$s کی پیداوار ہے۔ ھماری مہم ایک صحت مند کھلے انٹرنیٹ کو فروعٰ دینا ہے۔ Mozilla
+ مزید جانیے
]]>
+
+
+ رازداری اور سلامتی
+
+
+ سراغ راہ، کوکیاں، کوائف پسند
+
+
+ طے شدہ سیٹ کریں، خودکار مکمل
+
+
+
+
+ %1$s کے بارے میں، مدد
+
+
+ توسيعى سراغ کاری تحفظ
+
+
+ ویب مواد
+
+
+ ایپس تبدیل کرنا
+
+
+ عمومی
+
+
+ طے شدہ برائوزر، زبان
+
+
+ کوائف کا مجموعہ اور ان کا استعمال
+
+ تلاش کریں
+
+
+ تلاش تجاویز حاصل کریں
+
+
+ طے شدہ
+
+
+ تلاش انجن
+
+
+ چالو
+
+
+ بند
+
+
+ URL خودکار تکميل
+
+
+ بہترین سائٹس کے لیئے
+
+
+ سائٹس کے لئے حو آپ شامل کریں
+
+
+ سائٹس بندرست کریں
+
+
+ سائٹس بندرست کریں
+
+
+ تخصیصی URL کا + اظافہ کریں
+
+
+ URL شامل کریں۔
+
+
+ تخصیصی URL کا اظافہ کریں
+
+
+ تخصیصی URL کا اظافہ کریں
+
+
+ کوکیاں اور سائٹ کے کواِئف
+
+
+ کوائف پسند
+
+
+ مخصوص URL ہٹائیں
+
+
+ مزید سیکھیں
+
+
+ مخصوص خودکار مکمل URLs شامل اور بندرست کریں۔
+
+
+ URL اظافہ کرنے کے لیئے
+
+
+ URL چسپاں یا داخل کریں
+
+
+ مثال کے طور پر: mozilla.org
+
+
+ مثال: example.com
+
+
+ نئے تخصیصی URL کا اظافہ کر دیا گیا ہے۔
+
+
+ ہٹائیں
+
+
+ ہٹائیں
+
+
+ درج شدہ URL دو دفعہ پڑتال کریں۔
+
+ زبان
+
+ نظام طے شدہ
+
+ رازداری
+ اشتھار ٹریکرز کو بلاک کریں
+ چند اشتہاری ٹریکر سائَٹ دورہ کرتی ہیں حب کہ آپ نے اشتتہارات پر کلک بھی نہ کیا ہو
+ تجزیاتی ٹریکرز کو بلاک کریں
+ سرگرمی جیسے کہ تھپتپانا اور طورمار کرنا کی پہمائش اور تجزیہ کرنے کے لیئے استعمال ہوتا ہے
+ سماجی ٹریکرز بلاک کریں
+ دیگر مواد کے ٹڑیکرز بلاک کریں
+ اہل بنانے کی وجہ سے شاید چند صفحات غیر متوقع برتائو کر سکتے ہیں
+ کوکیز بلاک کری٘ں
+
+
+ نہیں شکریہ
+ صرف 3-فریق کی ٹریکر کوکیاں بلاک کریں
+ صرف 3-فریق کی کوکیاں بلاک کریں
+
+ جی ہاں برائے مہربانی
+
+
+ ایپ کو غیر مقفل کرنے کے لیے فنگر پرنٹ کا استعمال کریں۔
+
+
+ سٹلتھ
+
+ اہپس تبدیل کرنے وقت ویب صفحات کو چھپائیں اور اکرین شاٹ بلاک کرین۔
+
+ سلامتی
+
+ کارکردگی
+ ویب فونٹس بلاک کریں
+
+ نتیجتن اسکی شاید آئیکن یا تصاویر گایب ہو سکتی ہے
+
+ JavaScript بلاک کریں
+
+ صفحات شاید تیزی سے لوڈ ہوں، لیکن شاید غیر متوقع طور پر برتاؤ کریں
+
+
+ %1$s و طےشدہ براؤزر بنائیں
+
+ Mozilla
+ استعمال شدہ کوائف ارسال کریں
+
+
+ مزید سیکھیں
+
+
+ Mozilla صرف اس کو جمع کرنے کی کوشش کرتا ہے جو ہمیں ہر کسی کے لئے %1$s فراہم کرنے اور بہتر بنانے کی ضرورت ہے۔
+
+
+ رازداری کا نوٹس
+
+
+ لائسنس کی معلومات
+
+
+ لائبریریاں جو ہم استعمال کرتے ہیں
+
+
+ کے بارے میں%1$s
+
+
+ تنصیب شدہ تلاش انجن
+
+
+ تلاش انجن کا انتخاب کریں
+
+
+ طےشدہ تلاش انجن بحال کریں
+
+
+ ایک اور تلاش انجن کا اضافہ کریں
+ تلاش انجن ہٹائیں
+ ہٹائیں
+
+
+ ایک اور تلاش انجن کا اضافہ کریں
+
+ اپنا پسندیدہ انجن منتخب کریں
+
+
+ تلاش انجن اضافہ کریں
+
+ تلاش انجن کا نام
+ استعمال کرنے کے لیئے اسٹرنگ تلاش کریں
+ محفوظ کریں
+
+
+ مثلاٰ؛ example.com/search/?q=%s
+
+ نیا تلاش انجن شامل کر دیا گیا۔
+
+ تلاش انجن کا نام داخل کریں
+ ایک تنصیب شدہ تلاش کے انجن پہلے ہی اس نام کا استعمال کر رہا ہے۔
+
+ ایک سٹرنگ داخل کریں
+
+ پڑتال کریں کہ سرچ سٹرنگ مثال کی شکل سے میل کھاتا ہے
+
+
+ ان پٹ صاف کریں
+
+
+ برخاست کریں
+
+
+ برائوزنگ سابقات مٹائیں
+
+
+ %1$s ٹیبس کھلے ہیں
+
+
+ قابل بھروسا کنکشن
+
+
+ لوڈ ہو رہا ہے
+
+
+ ویبسائٹ لوڈ ہو گئی ہے
+
+
+ مزید اختیارات
+
+
+ مزید اختیارات کا بٹن
+
+
+ آگے کی جانب گشت کريں
+
+
+ ویب سائٹ دوبارہ لوڈ کریں
+
+
+ پیچھے کی طرف جائے
+
+
+ ویبسائَٹ لوڈ کرنا بند کریں
+
+
+ پچھلی ایپلیکیشن میں واپس جائیں
+
+
+ ٹریکرز کو بلاک کریں
+
+ آپ کے حقوق
+
+ کسی دوسری اپلیکیشن میں ربط کھولیں
+
+ اس ربط کو %2$s میں کھولنے کے لیےآپ %1$s چھوڑ سکتے ہیں۔
+
+ ایک ایپیلیکیشن تلاش کریں جو ربط کو کھول سکے
+
+ آپ کے آلہ پر کوئی بھی ایپلیکیثن اس ربط کو کھولنے کی اھل نہِں۔ آپ %1$s کو چھوڑ کر %2$s کو تلاش کریں ایک ایسی اپلی کیشن جو یہ کر سکتی ہو۔
+
+ نجی براؤزنگ سے خروج کریں؟
+
+
+ %1$s مکمل ہوا
+
+
+ کھولیں
+
+
+
+
+
+
+
+
+
+ پیش کار نہیں ملا
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ بند کریں
+
+
+ شروع کریں
+
+
+ آپ کی نجی نوعیت کو پاور اپ کرے
+
+ نجی برائوزنگ کو اگلی سطح پر لے کر جائیں۔ اشتہارات اور دیگر مواد جو آپ کی مختلف سائٹ پر سراغ راہ کاری کر کے صفحہ کہ لوڈ ہوبے کے وقت کو سست کر سکتا ہے۔
+
+
+ آپ کی تلاش، آپ کی طرز پر
+
+ کچھ مختلف ڈھونڈ رہے ہیں؟ سیٹنگوں سے دوسرے تلاش انجن کا انتخاب کریں۔
+
+
+ اپنی ابتدائی اسکرین پر تیز راہ کا ا ضافہ کریں
+
+ %1$s میں جلد ہی اپنی پسندیدہ سائٹ پر واپس آئیں۔ صرف %1$s مینو سے \"ابتدائی اسکرین پر شامل کریں\" کا انخاب کریں۔
+
+
+ رازداری کو عادت بنائیں
+
+ ٹھیک ہے، مجھے سمجھ آگئی ہے!
+ چھوڑ دیں
+ آگے
+
+
+ -
+
+
+ اظافہ کریں
+
+
+ جی ہاں
+
+
+ منسوخ کریں
+
+
+ نہیں
+
+
+ نجی برائوزنگ سیشن
+
+
+ اطلاعات آپ کو %1$s سیشن ایک تھاپ سے مٹا سکتے ہیں۔ آپ کو ایپلیکیشن کھولنے یا برائوزر میں کیا چل رہا ہے کی ظرورت نہیں۔
+
+
+ برائوزنگ سابقات مٹائیں
+
+
+ Firefox ڈائونلوڈ کریں
+
+
+ صارف کا نام
+ پاس ورڈ
+ صاف کریں
+
+
+
+ قابل بھروسا کنکشن
+ غیر مھفوظ کنکشن
+
+ توثیق کردہ برائے: %1$s
+
+
+ سائٹ سلامتی
+ URLپہلے ہی موجود ہے
+
+
+ صفحے میں ڈھونڈیں
+
+
+ صفحہ میں ڈھونڈیں
+
+
+ %2$d/%1$d
+
+ %1$d میں سے %2$d
+
+
+ اگلا نتیجہ تلاش کریں
+
+ پچھلا نتیجہ تلاش کرین
+
+ صفحہ میں تلاش کوبرخاست کریں
+
+
+
+
+ ڈیسک ٹاپ سائٹ کی درخواست کریں
+
+
+ ڈیسک ٹاپ سائٹ
+
+
+ نقل شدہ URL
+
+
+ تخلیق کار ٹول
+
+
+ ایپس میں ربط کھولیں
+
+
+ اعلٰی
+
+
+ سائٹ کی اجازتں
+
+
+ چالو
+
+
+ بند
+
+
+ آٹو پلے
+
+
+ اس کی اجازت دینے کے لئے:
+
+
+ 1. Android کی سیٹنگز پر جائیں
+
+
+ اجازتوں پر دبایں]]>
+
+
+ سیٹنگز پر جائیں
+
+
+ %1$s کو چالو کرنے کے لئے ٹو گل کریں]]>
+
+
+ کیمرہ
+
+
+ مائیکروفون
+
+
+ محل وقوع
+
+
+ اعلانات
+
+
+ اجازت کے لئے پوچھیں
+
+
+ بلاک کیا گیا
+
+
+ اجازت ہے
+
+
+ Android کے ذریعہ مسدود ہے
+
+
+ آڈیو اور ویڈیو کی اجازت دیں
+
+
+ صرف آڈیو بلاک کریں
+
+
+ تجویز کردہ
+
+
+ آڈیو اور ویڈیو کو مسدود کریں
+
+
+ مطالعہ
+
+
+ مزید سیکھیں
+
+
+ تبدیلیاں لاگو کرنے کے لیے ایپلیکیشن چھوڑ دی جائے گی۔
+
+
+ ہٹائیں
+
+
+ فعال
+
+
+ مکمل شدہ
+
+
+ "USB//Wi-Fi کے ذریعے بعید ٹھیک کاری "
+
+
+ ربط نئے سیشن میں کھولیں
+
+
+ فنگر پرنٹ آئکن
+
+
+ فنگر پرنٹ کی پہچان نہیں ہو سکی. دوبارہ کوشش کریں۔
+
+
+ انگلی بہت تیزی سے حرکت میں آئی۔ دوبارہ کوشش کریں.
+
+
+ تلاش تجاویز دکھائیں
+
+
+ نہیں
+
+
+ ہاں
+
+
+ کچھ تلاش انجن تجاویز نہیں دکھا سکتے۔
+
+
+ برخاست کریں
+
+
+
+
+ ابتدائی اسکرین پر تجاویز بند کریں
+
+
+ نیا ٹیب کھل گیا
+
+
+ سوئچ کریں
+
+
+ فوری طور پر نئے ٹیب میں لنک پر سوئچ کریں۔
+
+
+ ممکنہ طور پر خطرناک اور دھوکہ دہی والی سائٹوں کو بلاک کریں۔
+
+
+ استثنیات
+
+ اس ویب ساِئٹس کے لئے آپ نے مواد روکنا غیر فعال کر دیا ہے۔
+
+ ہٹائیں
+
+ تمام ویب سائٹیں ہٹائیں
+
+
+ کوکیز بلاک کری٘ں
+
+
+ کیا آپ کوکیز کو بلاک کرنا چاہتے ہیں؟
+
+
+ ٹیب تباہ ہوگیا
+
+ معذرت ہمیں اس ٹیب کے ساتھ ایک مسئلہ درپیش ہے۔
+
+ ایک نجی براؤزر کے طور پر، ہم کبھی بھی اس ٹیب کو محفوظ یا بحال نہیں کرسکتے ہیں۔
+
+ ٹیب بند کر دیں
+
+
+ تباہی کی رپورٹ Mozilla کو ارسال کریں
+
+
+
+
+ ٹریکرز %s سے بلاک ہیں۔
+
+ مواد
+
+ اشتہار
+
+ سماجی
+
+ تجزیہ
+
+ توسيعى سراغ کاری تحفظ
+
+ اس سائٹ کیلئے تحفظات بند ہیں
+
+ اس سائٹ کیلئے تحفظات چالو ہیں
+
+ کنکشن محفوظ ہے۔
+
+ کنکشن محفوظ نہیں ہے
+
+
+ بلاک کرنے کے لیے ٹریکرز اور اسکرپٹ
+
+
+ واپس جائیں
+
+
+
+ ہٹائیں
+
+
+ نیا نام دیں
+
+ نیا نام دیں
+
+ شارٹکٹ کے نام
+
+
+
+ تھیم
+
+ ہلکا
+
+ گہرا
+
+ بیٹری سیور کے ذریعہ مرتب کردہ
+
+ آلہ کے تھیم پر عمل کریں
+
+
+ ناقابل بھروسا کنکشن
+
+
+
+ ٹیب بند کریں
+
+
+ پاپ اپ بند کریں۔
+
+
+
+ آپ محفوظ ہیں!
+
+ برخاست کریں
+
+
+
+
+ بند کریں
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-vi/strings.xml b/mobile/android/focus-android/app/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000000..3f7833a83a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-vi/strings.xml
@@ -0,0 +1,1120 @@
+
+
+
+
+
+
+
+
+ Hủy bỏ
+
+ OK
+
+ Lưu
+
+
+ Tìm kiếm hoặc nhập địa chỉ
+
+ Duyệt web riêng tư.\nDuyệt. Xóa. Lặp lại.
+
+
+ Lịch sử duyệt web của bạn đã bị xóa.
+ Lịch sử duyệt web đã bị xóa
+
+
+ Lịch sử duyệt web của thẻ đã bị xóa.
+
+
+ Tìm kiếm %1$s
+
+
+ Chia sẻ…
+
+
+ Báo cáo vấn đề về trang
+
+
+ Mở bằng %1$s
+
+
+ Mở bằng…
+
+
+ Thêm vào màn hình chính
+
+
+ Thêm vào lối tắt
+
+ Xóa khỏi lối tắt
+
+
+ Cài đặt
+ Giới thiệu
+ Trợ giúp
+ Quyền lợi của bạn
+
+
+ Trình theo dõi đã chặn
+
+
+ Tắt tính năng này có thể khắc phục một số vấn đề về trang web
+
+
+ Chặn nội dung
+
+ Tắt để khắc phục lỗi một số trang web
+
+
+ Được hỗ trợ bởi %1$s
+
+
+ Chia sẻ qua
+
+ Xóa lịch sử duyệt web?
+ Nhấn hoặc xóa thông báo này để xóa lịch sử duyệt web của bạn một cách an toàn.
+
+
+ Nhấn hoặc vuốt thông báo này để xóa lịch sử duyệt web của bạn một cách an toàn.
+
+ Xóa lịch sử duyệt web
+
+
+ Mở
+
+
+ Xóa và mở
+
+
+ Xóa
+
+
+ Xóa lịch sử duyệt web
+
+
+
+ Xóa & mở
+
+
+ Xóa và mở %1$s
+
+
+
+ Tìm kiếm trong Focus
+
+ Tìm kiếm trong Klar
+
+ Tìm kiếm trong Focus Beta
+
+ Tìm kiếm trong Focus Nightly
+
+
+ %1$s giúp bạn kiểm soát mọi thứ.
+Sử dụng nó như một trình duyệt riêng tư:
+
+ Tìm kiếm và duyệt ngay trong ứng dụng
+ Chặn các trình theo dõi (hoặc cập nhật cài đặt để cho phép các trình theo dõi)
+ Xóa cookie cũng như lịch sử tìm kiếm và duyệt web khi xong phiên làm việc
+
+
+%1$s được sáng lập bởi Mozilla. Nhiệm vụ của chúng tôi là thúc đẩy một Internet lành mạnh, cởi mở.
+Tìm hiểu thêm
]]>
+
+
+ Quyền riêng tư & bảo mật
+
+
+ Theo dõi, cookie, lựa chọn dữ liệu
+
+
+ Đặt làm mặc định, tự đồng điền
+
+
+
+
+ Giới thiệu về %1$s, trợ giúp
+
+
+ Trình chống theo dõi nâng cao
+
+
+ Nội dung web
+
+
+ Chuyển đổi ứng dụng
+
+
+ Chung
+
+
+ Trình duyệt, ngôn ngữ mặc định
+
+
+ Thu thập & sử dụng dữ liệu
+
+ Tìm kiếm
+
+
+ Nhận gợi ý tìm kiếm
+
+ %1$s sẽ gửi nội dung bạn nhập vào thanh địa chỉ đến công cụ tìm kiếm của bạn
+
+
+ Mặc định
+
+
+ Công cụ tìm kiếm
+
+
+ Bật
+
+
+ Tắt
+
+
+ URL tự động điền
+
+
+ Với các trang web hàng đầu
+
+
+ Cho phép để %s tự động hoàn thành hơn 450 URL phổ biến trong thanh địa chỉ.
+
+
+ Với trang web bạn thêm
+
+
+ Bật để %s tự động hoàn thành các URL yêu thích của bạn.
+
+
+ Quản lý trang web
+
+
+ Quản lý trang web
+
+
+ + Thêm URL tùy chỉnh
+
+
+ Danh sách tự động điền của bạn:
+
+
+ Thêm URL
+
+
+ Thêm URL tùy chỉnh
+
+
+ Thêm URL tùy chỉnh
+
+
+ Thêm liên kết vào tự động điền
+
+
+ Cookie và dữ liệu trang
+
+
+ Lựa chọn dữ liệu
+
+
+ Loại bỏ các URL tùy chỉnh
+
+
+ Tìm hiểu thêm
+
+
+ Thêm và quản lý các URL tùy chỉnh tự động điền.
+
+
+ URL muốn thêm
+
+
+ Dán hoặc nhập URL
+
+
+ Ví dụ: mozilla.org
+
+
+ Ví dụ: example.com
+
+
+ URL tùy chỉnh mới đã được thêm vào.
+
+
+ Loại bỏ
+
+
+ Loại bỏ
+
+
+ Kiểm tra lại URL mà bạn đã nhập.
+
+ Ngôn ngữ
+
+ Mặc định của hệ thống
+
+ Riêng tư
+ Chặn trình theo dõi quảng cáo
+ Một số quảng cáo theo dõi lượt truy cập trang, ngay cả khi bạn không click vào quảng cáo
+ Chặn trình theo dõi phân tích
+ Được sử dụng để thu thập, phân tích và đo lường các hoạt động như chạm và cuộn trang
+ Chặn trình theo dõi xã hội
+ Được nhúng vào các trang để theo dõi lượt truy cập của bạn và để hiển thị tính năng chẳng hạn như các nút chia sẻ
+ Chặn trình theo dõi khác
+ Việc kích hoạt có thể khiến một số trang hoạt động không như mong đợi
+ Chặn cookie
+
+
+ Không, cảm ơn
+ Chỉ chặn cookie theo dõi của bên thứ ba
+ Chỉ chặn cookie bên thứ ba
+
+ Chặn cross-site cookies
+ Có
+
+
+ Sử dụng vân tay để mở khóa ứng dụng
+
+
+ Mở khóa bằng vân tay nếu bạn đã thêm lối tắt hoặc khi một trang web đã mở trong %s.
+
+
+ Chế độ ẩn
+
+ Ẩn trang web khi chuyển sang ứng dụng khác và chặn chụp ảnh màn hình.
+
+ Bảo mật
+
+ Hiệu suất
+ Chặn phông chữ trang Web
+
+ Có thể dẫn đến thiếu biểu tượng hoặc hình ảnh
+
+ Chặn JavaScript
+
+ Trang có thể tải nhanh hơn, nhưng cũng có thể hoạt động thiếu ổn định
+
+
+ Đặt %1$s làm trình duyệt mặc định
+
+ Mozilla
+ Gửi dữ liệu sử dụng
+
+
+ Tìm hiểu thêm
+
+
+ Mozilla chỉ thu thập những gì chúng tôi cần cung cấp và cải tiến %1$s cho tất cả mọi người.
+
+
+ Chính sách riêng tư
+
+
+ Thông tin giấy phép
+
+
+ Thư viện mà chúng tôi sử dụng
+
+
+ %s | Thư viện OSS
+
+
+ Thông tin về %1$s
+
+
+ Công cụ tìm kiếm đã cài đặt
+
+
+ Chọn công cụ tìm kiếm
+
+
+ Khôi phục công cụ tìm kiếm mặc định
+
+
+ + Thêm công cụ tìm kiếm khác
+ Gỡ bỏ công cụ tìm kiếm
+ Gỡ bỏ
+
+
+ Thêm công cụ tìm kiếm khác
+
+ Chọn công cụ ưa thích của bạn:
+
+
+ Thêm công cụ tìm kiếm
+
+ Tên công cụ tìm kiếm
+ Chuỗi tìm kiếm để sử dụng
+ Lưu
+
+
+ Ví dụ: example.com/search/?q=%s
+
+ Đã thêm công cụ tìm kiếm mới.
+
+ Nhập tên công cụ tìm kiếm
+ Một công cụ tìm kiếm đã cài đặt đã sử dụng cùng tên này.
+
+ Nhập chuỗi tìm kiếm
+
+ Kiểm tra xem chuỗi tìm kiếm có đúng với định dạng như ở ví dụ không
+
+
+ Xóa trắng
+
+
+ Bỏ qua
+
+
+ Xóa lịch sử duyệt web
+
+
+ Thẻ đang mở: %1$s
+
+
+ Kết nối an toàn
+
+
+ Đang tải
+
+
+ Đã tải xong trang web
+
+
+ Tùy chọn thêm
+
+
+ Nút tùy chọn khác
+
+
+ Điều hướng tiến
+
+
+ Tải lại trang web
+
+
+ Điều hướng lùi
+
+
+ Dừng tải trang web
+
+
+ Quay về ứng dụng trước đó
+
+
+ Số trình theo dõi đã chặn
+
+
+ Chặn theo dõi
+
+ Quyền lợi của bạn
+
+ Mở đường dẫn trong ứng dụng khác
+
+ Bạn có thể rời khỏi %1$s để mở liên kết này trong %2$s.
+
+ Tìm ứng dụng có thể mở liên kết này
+
+ Không có ứng dụng nào trên thiết bị của bạn có thể mở liên kết này. Bạn có thể rời khỏi %1$s để tìm ứng dụng có thể mở %2$s.
+
+ Thoát duyệt web riêng tư?
+
+
+ Đã tải xong %1$s
+
+
+ Mở
+
+
+
+
+
+
+
+
+
+
+ Đã thêm vào lối tắt!
+
+ Không tìm thấy máy chủ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Đóng
+
+
+
+ Chào mừng đến với %1$s
+
+
+ Nhanh. Riêng tư. Không có phiền nhiễu.
+
+
+ Bắt đầu
+
+
+
+ %1$s không giống các trình duyệt khác
+
+
+ Chúng tôi xóa lịch sử của bạn khi bạn đóng ứng dụng để có thêm quyền riêng tư.
+
+
+
+ Đặt %1$s làm trifnh duyệt mặc định để bảo vệ dữ liệu của bạn với mọi liên kết bạn mở.
+
+
+ Đặt làm trình duyệt mặc định
+
+
+ Bỏ qua
+
+
+
+ Tăng quyền riêng tư của bạn
+
+ Duyệt web ở chế độ riêng tư để nâng cao bảo mật. Giúp làm giảm thời gian tải trang và chặn các quảng cáo và nội dung mà có thể theo dõi bạn trên các trang web.
+
+
+ Tìm kiếm của bạn, theo cách của bạn
+
+ Đang tìm kiếm với cái gì khác? Chọn công cụ tìm kiếm khác thành mặc định trong Cài đặt.
+
+
+ Thêm lối tắt tới màn hình chính của bạn
+
+ Quay lại trang web của bạn nhanh hơn trong %1$s. Chỉ cần chọn \"Thêm vào màn hình chính\" trong menu %1$s.
+
+
+ Làm cho sự riêng tư thành một thói quen
+
+ Đặt %1$s làm trình duyệt mặc định của bạn và nhận được lợi ích của duyệt web riêng tư khi bạn mở các trang web từ các ứng dụng khác.
+
+ Đã hiểu!
+ Bỏ qua
+ Tiếp
+
+
+ -
+
+
+ Thêm
+
+
+ CÓ
+
+
+ Hủy bỏ
+
+
+ KHÔNG
+
+
+ Lối tắt sẽ mở khi đã tắt tính năng Trình chống theo dõi nâng cao
+
+
+ Phiên duyệt web riêng tư
+
+
+ Thông báo cho phép bạn xóa phiên làm việc của %1$s với một cú nhấp. Bạn không cần mở ứng dụng hoặc xem những gì đang chạy trong trình duyệt của bạn.
+
+
+ Xóa lịch sử duyệt web
+
+
+ Tải Firefox
+
+
+
+
+
+
+
+
+ Giấy phép Công cộng Mozilla và các giấy phép mã nguồn mở khác.]]>
+
+
+ tại đây.]]>
+
+
+ giấy phép nguồn mở miễn phí khác.]]>
+
+
+ GNU General Public License v3 và có sẵn ở đây .]]>
+
+
+ Tên đăng nhập
+ Mật khẩu
+ Xóa
+
+
+
+ Kết nối an toàn
+ Kết nối không an toàn
+
+ Xác minh bởi: %1$s
+
+
+ Bảo mật trang web
+ URL đã tồn tại
+
+
+ Tìm trong trang
+
+
+ Tìm trong trang
+
+
+ %1$d/%2$d
+
+ %1$d trong số %2$d
+
+
+ Tìm kiếm kết quả tiếp theo
+
+ Tìm kiếm kết quả trước đó
+
+ Loại bỏ tìm kiếm trong trang
+
+
+
+
+ Yêu cầu trang web cho máy tính
+
+
+ Trang web cho máy tính
+
+
+ Đã sao chép URL
+
+
+ Công cụ nhà phát triển
+
+
+ Mở liên kết trong ứng dụng
+
+
+ Nâng cao
+
+
+ Quyền hạn trang web
+
+
+ Giảm biểu ngữ cookie
+
+
+ Bật
+
+
+ Tắt
+
+
+ Giảm biểu ngữ cookie
+
+
+ Xem ít biểu ngữ hơn bằng cách tự động từ chối yêu cầu cookie, khi có thể.
+
+ -->
+ Giảm biểu ngữ cookie
+
+
+ BẬT cho trang web này
+
+
+ Trang web hiện không được hỗ trợ
+
+
+ TẮT cho trang web này
+
+
+ Giảm biểu ngữ cookie
+
+
+ TẮT cho trang web này
+
+
+ BẬT cho trang web này
+
+
+ Bật giảm biểu ngữ cookie cho %1$s?
+
+
+ Tắt giảm biểu ngữ cookie cho %1$s?
+
+
+ %1$s sẽ xóa cookie của trang web này và làm mới trang. Xóa tất cả cookie có thể khiến bạn đăng xuất hoặc làm trống giỏ hàng.
+
+
+ %1$s có thể thử tự động từ chối yêu cầu cookie.
+
+
+ Trang web này hiện không được hỗ trợ bởi Giảm biểu ngữ cookie. Bạn có muốn yêu cầu nhóm của chúng tôi xem xét trang web này và thêm hỗ trợ trong tương lai không?
+
+
+ Hủy bỏ
+
+
+ Yêu cầu hỗ trợ
+
+
+ Đã gửi yêu cầu hỗ trợ trang web.
+
+
+ Đã gửi yêu cầu hỗ trợ trang web.
+
+
+
+ %1$s cố gắng từ chối yêu cầu cookie để loại bỏ biểu ngữ cookie gây phiền nhiễu.\n\nQuản lý tùy chỉnh biểu ngữ cookie trong %2$s.
+
+ cài đặt
+
+
+ Tự động phát
+
+
+ Để cho phép nó:
+
+
+ 1. Đi đến Cài đặt Android
+
+
+ Quyền]]>
+
+
+ Đi đến Cài đặt
+
+
+ %1$s sang BẬT]]>
+
+
+ Máy ảnh
+
+
+ Micrô
+
+
+ Vị trí
+
+
+ Thông báo
+
+
+ Nội dung DRM được kiểm soát
+
+
+ Hỏi để cho phép
+
+
+ Đã chặn
+
+
+ Đã cho phép
+
+
+ Bị chặn bởi Android
+
+
+ Cho phép âm thanh và video
+
+
+ Chỉ chặn âm thanh
+
+
+ Được đề xuất
+
+
+ Chặn âm thanh và video
+
+
+ Nghiên cứu
+
+
+ Firefox có thể cài đặt và chạy các nghiên cứu theo thời gian.
+
+
+ Tìm hiểu thêm
+
+
+ Ứng dụng sẽ thoát để áp dụng các thay đổi
+
+
+ Xóa
+
+
+ Hoạt động
+
+
+ Hoàn tất
+
+
+ Gỡ lỗi từ xa qua USB/Wi-Fi
+
+
+ Mở khóa
+
+
+ Xác nhận bằng vân tay của bạn
+
+
+ Bạn có thể sử dụng vân tay của mình để tiếp tục phiên ứng dụng hiện tại của mình.
+
+
+ Mở liên kết trong phiên mới
+
+
+ Biểu tượng vân tay
+
+
+ Không thể nhận dạng vân tay. Vui lòng thử lại.
+
+
+ Ngón tay đã di chuyển quá nhanh. Vui lòng thử lại.
+
+
+ Hiển thị đề xuất tìm kiếm?
+
+
+ Để nhận đề xuất, %1$s cần gửi nội dung bạn nhập vào thanh địa chỉ tới công cụ tìm kiếm.
+
+
+ Không
+
+
+ Có
+
+
+ Một vài công cụ tìm kiếm có thể không hiện gợi ý.
+
+
+ Bỏ qua
+
+
+
+
+ Trang web hoạt động không như mong đợi?\n Hay thử tắt Trình chống theo dõi
+
+
+ Thêm vào Màn hình chính]]>
+
+
+ Mở mọi liên kết trong %1$s\n Đặt %1$s làm trình duyệt mặc định
+
+
+ Tự động điền URLs cho các trang web bạn sử dụng\n Nhấp và giữ bất kỳ URL trong thanh địa chỉ
+
+
+ Mở liên kết trên thẻ mới\n Nhấp và giữ bất kì liên kết nào trên trang
+
+
+ Tắt mẹo trên màn hình bắt đầu
+
+
+ Đã mở thẻ mới
+
+
+ Chuyển
+
+
+ Đã vào chế độ toàn màn hình
+
+
+ Chuyển sang liên kết trong thẻ mới ngay lập tức
+
+
+ Chặn các trang web nguy hiểm và lừa đảo
+
+ Chặn các trang web được báo cáo là lừa đảo và tấn công, trang web phần mềm độc hại và các trang web phần mềm không mong muốn.
+
+
+ Chế độ chỉ HTTPS
+
+
+ Tự động cố gắng kết nối với các trang web bằng giao thức mã hóa HTTPS để tăng cường bảo mật.
+
+
+ Ngoại lệ
+
+ Bạn đã tắt chế độ Chặn nội dung cho các trang web này.
+
+ Xóa
+
+ Xóa tất cả các trang web
+
+
+ Chặn cookie
+
+
+ Bạn có muốn chặn cookie?
+
+
+ Thẻ bị hỏng
+
+ Rất tiếc. Chúng tôi đã gặp sự cố với thẻ này.
+
+ Trong trình duyệt riêng tư, chúng tôi không bao giờ lưu và không thể khôi phục thẻ này.
+
+ Đóng thẻ
+
+
+
+
+
+ Gửi báo cáo lỗi đến Mozilla
+
+
+
+
+ Trình theo dõi bị chặn kể từ %s
+
+ Nội dung
+
+ Quảng cáo
+
+ Xã hội
+
+ Phân tích
+
+ Trình chống theo dõi nâng cao
+
+ Bảo vệ đã TẮT cho trang web này
+
+ Bảo vệ đã BẬT cho trang web này
+
+ Kết nối an toàn
+
+ Kết nối không an toàn
+
+
+ Trình theo dõi và script để chặn
+
+
+ Quay lại
+
+
+
+ Xóa
+
+
+ Đổi tên
+
+ Đổi tên
+
+
+ Tên lối tắt
+
+
+ Ảnh đã lưu và chia sẻ <b>sẽ không bị xóa</b> khi bạn xóa lịch sử %1$s
+
+
+
+ Chủ đề
+
+ Sáng
+
+ Tối
+
+ Đặt theo trình tiết kiệm pin
+
+ Đặt theo chủ đề thiết bị
+
+
+
+ Trang web này không hỗ trợ HTTPS
+
+
+ Tìm hiểu thêm
+ Thay đổi cài đặt này trong Cài đặt > Riêng tư & bảo mật > Bảo mật.]]>
+
+
+ Kết nối không an toàn
+
+
+
+ Nếu trước đây bạn đã kết nối thành công với máy chủ này, lỗi có thể là tạm thời.
+ ]]>
+
+
+ Ai đó có thể đang cố mạo danh trang web và việc tiếp tục có thể gặp rủi ro.
+
+ %1$s không tin tưởng %2$s bởi vì tổ chức phát hành chứng chỉ của nó không xác định, chứng chỉ tự ký hoặc máy chủ không gửi đúng chứng chỉ trung gian.
+ ]]>
+
+
+
+ Đóng thẻ
+
+
+
+ Bắt được rồi! Chúng tôi đã ngăn trang web này theo dõi bạn. Nhấn vào tấm khiên bất kỳ lúc nào để xem những gì chúng tôi đang chặn.
+
+
+ Đóng cửa sổ bật lên
+
+
+
+ Bạn đã được bảo vệ!
+
+ Các cài đặt mặc định này cung cấp khả năng bảo vệ mạnh mẽ. Tuy nhiên, thật dễ dàng để điều chỉnh cài đặt để đáp ứng các nhu cầu cụ thể của bạn.
+
+ Bỏ qua
+
+
+ Nhấn vào đây để xóa tất cả — lịch sử, cookie, mọi thứ — và bắt đầu làm mới trên một thẻ mới.
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ Đóng
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ Widget tìm kiếm
+
+ !-- This is the title of promote search widget dialog. -->
+
+ Đã xóa lịch sử duyệt web! 🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Bắt đầu phiên duyệt web riêng tư của bạn và chúng tôi sẽ chặn trình theo dõi và các nội dung xấu khác khi bạn di chuyển.
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ Chúng tôi sẽ để bạn ở chế độ duyệt web riêng tư, nhưng hãy bắt đầu nhanh hơn vào lần sau với tiện ích %1$s trên màn hình chính của bạn.
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ Thêm widget vào màn hình chính
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ Đã thêm widget vào màn hình chính
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-wo/strings.xml b/mobile/android/focus-android/app/src/main/res/values-wo/strings.xml
new file mode 100644
index 0000000000..73c23f2250
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-wo/strings.xml
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+ Neenal
+ OK
+
+ Denc
+
+ Seet walla dugal ag màkkaan
+
+ Njoow ci sutura doxal boppam.\nJoow. Far. Tambaliwaat.
+
+ Farees na jaar-jaaru sag njoow.
+
+ Seet %1$s
+
+ Séddoo…
+
+ Yokk ci njëlbéenu sa xoolu
+
+ Jekk-jekkal
+ Ñeel
+ Ndimbal
+ Say yelleef
+
+ Téyees na toppaatekat yi
+
+ Séddoo ci
+
+ Far jaar-jaaru njoow gi
+
+ Ubbi
+
+ Far te Ubbi
+
+ Far
+
+ Far jaar-jaaru njoow gi
+
+
+ Seet
+
+ Wàccaale
+
+ Taalees
+
+ Fayees
+
+ Àggale mbindum màkkaan gu boppu
+
+ + Yokkal ag màkkaan gees jëmmal
+
+ Yokkal ag màkkaan gees jëmmal
+
+ Dindi màkkaan gees Jëmmal
+
+ Gën a xam
+
+ Yokkal te yor mottali gu boppu yu màkkaan yees jëmmal.
+
+ Màkkaan gees war a yokk
+
+ Tafal wala bind ag màkkaan
+
+ Misaal: misaal.com
+
+ Yokkees na màkkaan gees jëmmal gu bees.
+
+ Far
+
+ Far
+
+ Xoolaatal màkkaan gi nga bind.
+
+ Làkk
+ Wàccaaleeg noste gi
+
+ Sutura
+ Téye toppkati siiwal
+ Yenn siiwal yi, donte bësoo ci, man nañoo topp dal yi ngay ubbi
+ Téye toppkati xayma
+ Dees leen di jëfandikoo ngir dajale, saytu ak natt yëngu-yëngu yu mel ni bës walla yéegal-wàcce aw xët
+ Téye toppkati mbaali mboolaay
+ Dees leen di dugalaale ci dali web yi ngir topp sag njoow ak di wone ay solo niki bësu ngir séddoo
+ Téye yaneen toppkati ëmbéef
+ Doxal gi man naa jur yenn xëti web yi am doxaliin gees foogulwoon
+
+ Nëbbu
+ Nëbb xëti web yi sooy jàll ci beneen jëfekaay
+
+ Gaawaay
+ Téye loyi web yi
+ Man naa jur njunj walla péeñ yu dul feeñ
+
+ Def %1$s muy sa joowukaay bu njëkk
+
+ Mozilla
+ Yónneey njoxe ci njëfandikoo gi
+
+ Gën ce xam
+
+ Mozilla li mu soxla rekk, te manul ñàkk, lay jéem a dajale ngir man a gënal %1$s ngir nun ñépp.
+
+ Sàrti Sutura
+
+ Ñeel %1$s
+
+ Seetukaay yiñ fi sàmp
+
+ Delloo seetukaay yi na ñu meloon
+
+ + Yokk ab seetukaay
+ Far ay seetukaay
+ Far
+
+ Yokk ab seetukaay
+
+ Turu seetukaay bi
+ Mbindum seet mees di jëfandikoo
+ Denc
+
+ Misaal: misaal.com//search/?q=%s
+
+ Yokkees na seetukaay bu bees.
+
+ Duggalal turu seetukaay bi
+
+ Duggalal mbind mi ngay seet
+
+ Seetal ndax mbindum sa ceet méngoo naak melokaanu Misaal mi
+
+ Far mbind mi
+
+ Tëj
+
+ Far jaar-jaaru njoow gi
+
+ Làcc yi ubbeeku: %1$s
+
+ Lonkoo gu wóor
+
+ Mi ngi yeb
+
+ Yebees na dalu web bi
+
+ Yeneen tànnéef
+
+ Dem ci kanam
+
+ Yebaat dalu web bi
+
+ Dellu ginnaaw
+
+ Dag dali web biy yeebu
+
+ Déllu ci app bi kë jiitu
+
+ Tëyyé trackers yi
+
+ Sa wareef
+
+ Ubbil lëkkalekaay bi ci beneen jëfekaay
+ Man ngaa génn %1$s ngir ubbi bii lëkkalekaay ci %2$s.
+ Seet ab jëfekaay bu man a ubbu lëkkalekaay bi
+ Amul benn jëfekaay ci sa jumtukaay bu man a ubbi bii lëkkalekaay. Man ngaa génn %1$s ngir seet ci %2$s ab jëfekaay bu ko man.
+ Génn ci Njoow gu Suturloo gi?
+
+ %1$s mat na
+
+ Ubbi
+
+
+
+
+
+
+
+
+
+ Giseesul joxekaay bi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dooleel sa sutura
+ Yóbbul njoow ci sutura ci geneen tolluwaay. Téyeel siiwal yeek yeneen ëmbeef yi lay man topp ci yeneen dal yi te di yeexal yebug xët yi.
+
+ Seet ak noo ko bëggee
+ Leneen ngay seet? Tànnal beneen seetukaay ci jekk-jekkal yi.
+
+ Yokki lëkkalekaay ci njëlbeenu sa xoolu
+ Dellu ci say dal yi nga taamu ci %1$s ci lu gaaw. Doy na nga bës ci \"Yokk ci njëlbeenu xoolu bi\" ci njëlu %1$s.
+
+ Def joow ci sutura doon jikko
+ Def %1$s sa joowukaay bu njëkk te jariñoo mbaaxi njoow ci sutura sooy ubbi xëti web ci yeneen jëfekaay.
+
+ OK, Dégg naa la!
+ Tëb
+ Toftal
+
+ -
+
+ Yokk
+
+ Neenal
+
+ Ubbig njoow ci sutura
+
+ Yëgle yi di nañ la may ngay man di far njoxéef yi ci sa %1$s ci benn bës rek. Soxlawoo ubbi jëfekaay walla xool liy dox ci sa joowukaay.
+
+ Far jaar-jaaru njoow gi
+
+ Yebbi Firefox
+
+ %1$s jëfekaay bu amul-fay te ubbeeku la bu Mozilla ak yeneen aji-barkeelu defar.
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-yua/strings.xml b/mobile/android/focus-android/app/src/main/res/values-yua/strings.xml
new file mode 100644
index 0000000000..1bbc5049a4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-yua/strings.xml
@@ -0,0 +1,479 @@
+
+
+
+
+
+
+
+
+ Ma’ u beetal
+
+ Uts
+
+ Táabsaj
+
+
+ Kaxbil wa oksaj tu\'uxi’
+
+ Ku maan tu juunaj chen a wóok\'sal.\nMaanen. Púustej. Tu ka\'aten.
+
+
+ Lúubsa\'ab tuláakal ba\'ax ta kaxtej.
+
+
+ Kaxbil %1$s
+
+
+ Ka túuxtik ti\'…
+
+
+ A\'al u táalamil te\'e túux tan kaxtiko\'
+
+
+ Je\'eb ich %1$s
+
+
+ Je\'eb ich…
+
+
+ Ts\'áabak tu\'ux u yila\'al tun káajal
+
+ Máak’anbil
+ Yo\'sal
+ Áantaj
+ A tojbe\'enilo\'ob
+
+
+ Kaxan ba\'al tupa\'anilo\'ob
+
+
+ Léench’inta’ab yéetel %1$s
+
+
+ Túuxbil yéetel
+
+
+ Lúubsal tuláakal tu\'ux maanech
+
+
+ Je\'ebek
+
+
+ Tuupuk ts\'óokole\' ka je\'ebek
+
+
+ Tuupuk
+
+
+ Lúubsal tuláakal tu\'ux maanech
+
+
+
+ Tuupuk ts\'óokole\' ka je\'ebek
+
+
+ Tuupuk ts\'óokole\' ka je\'ebek %1$s
+
+
+
+ Ta wóok’sal yéetel Jeets’el óol
+
+
+ Bixil ka kaxantik ba’alob, cookies, yéetel péeksilil
+
+
+ Ts’íibta’ab le ta yáax ts’íibo’, ka chuupuk tu juunaj
+
+
+
+
+ Yóok’sal le %1$s, áantaj
+
+
+ Ba’alob U ba’il web
+
+
+ K’exlantik biilalo’ob
+
+
+ Much’lantik péeksililo’ob yéetel bix beelankil
+
+ Kaxta’ak
+
+
+ Le ta yáax ts’íibo’
+
+
+ Meyajta’al
+
+
+ Ma’ tu meyajta’al
+
+
+ Chuupuk tu juunaj URL
+
+
+ + Ts’áabak URL tsikbe’eniltal
+
+
+ Ts’áabak URL tsikbe’eniltal
+
+
+ Ts’áabak URL tsikbe’eniltal
+
+
+ Cookies yéetel u péeksilil le tu’uxilo’
+
+
+ Jela’anil péeksilo’ob
+
+
+ Tuupuk le URL tsikbe’enilta’ab
+
+
+ K’ajóoltal u yaanali’
+
+
+ Ts’áabak bey xan kaxáantak le chuupuk tu juunal le URL tsikbe’enilta’ab.
+
+
+ URL uti’al u ts’áabak
+
+
+ Tak’bil wa oksa’ak u ts’íibtal URL
+
+
+ E’esajil: mozilla.org
+
+
+ E’esajil: example.com
+
+
+ Túumben URL tsikbe’eltan ts’óok u ts’áabak.
+
+
+ Tuupuk
+
+
+ Tuupuk
+
+
+ Ka’a ilej le URL ta ts’íibtaj.
+
+ T’aanil
+
+ U biilal yáax ts’íibtaj ti’ le nuupankil meyajilo’
+
+ Chen tu wóok’sale’
+ Ma’ meyajta’ab le kaxan ba’al uti’al koonol ba’alob
+ Yáan koonol ba’alob ku kaxantik a xíimbalil ich le tu’uxilo’, kex ma’ tan péets’ik yóok’olo’
+ Ma’ meyajta’al le kaxan ba’al túukulnaj
+ Beelankil uti’al moolbil, túukulbil yéetel p’iisbil péeksilo’ob je’e bey le ku máachaj bey xan ku jakchajal
+ Ma’ meyajta’al le kaxan ba’al uti’al tsikbal máak
+ Jupa’an ich je’e tu’uxake’ uti’al u kaxantik tu’ux ka maan ich web, bey xan uti’al u ye’esiktech bixilo’ob je’e bey le ka ts’áank’abtik uti’al a túuxtik ba’alob
+ Ma’ meyajta’ab le kaxan ba’al uti’al jejelas ba’ilob
+ Wa ka meyajtike’ ju beytal u k’as jela’antik u meyaj jejelas tseel ju’unilob
+ Ma’ meyajta’ak cookies
+
+ Ma’ meyajta’ak chen cookies ti’ yaanalilo’ob
+
+
+ Mun yila’al
+
+ Ta’akbesa’al le tseel ju’unob web ken u káajal u k’exel biilalilo’ob.
+
+ Buka’ajil
+ Ma’ meyajta’al u sayab web
+
+ Je’el u beytal u sa’atal le woojilo’ob wa le oochelo’ob
+
+ Ma’ meyajta’ak JavaScript
+
+ Le tseel ju’unob web je’el u beytal u meyaj séebanil chen ba’ale’ je’el u beytal u k’as k’exik u meyaj xan
+
+
+ Yéeytej %1$s ka p’àatak bey a suuk a maan ich web yéeteli’
+
+ Mozilla
+ Túuxtej buka’aj ts’óok a xupik
+
+
+ K’ajóoltal u yaanali’
+
+
+ Mozilla ku kaxantik u molik chen le jach k’áabetaj uti’al u ts’a’ik yéetel u ma’alobtal %1$s ti’ tuláakalilo’ob.
+
+
+ Ts’áab ojelbil bix u kana’antal ati’alo’
+
+
+ Yóok’sal le %1$s
+
+
+ U múuk’il ti’ u maan máak ich web ts’oka’anilob
+
+
+ Ts’áabak tu ka’aten le u yáax múuk’il ti’ u kaxan ba’al máak ich web
+
+
+ + Tsàabak u láak’ u múuk’il ti’ u kaxan ba’al máak ich web
+ Túupuk u múuk’il ti’ u kaxan ba’al máak ich web
+ Tuupuk
+
+
+ Tsàabak u múuk’il ti’ u kaxan ba’al máak ich web
+
+ U k’áaba’ ti’ u múuk’il ti’ u kaxan ba’al máak ich web
+ Kaxbil nuupul uti’al meyajta’al
+ Táabsaj
+
+
+ E’esajil: example.com/search/?q=%s
+
+ Ts’óok u tsàabal túumben u múuk’il ti’ u kaxan ba’al máak ich web.
+
+ Oksa’ak u k’áaba’ ti’ le u múuk’il ti’ u maan máak ich web
+ Junp’éel le u múuk’il ti’ u kaxan ba’al máak ich webe’ ts’óok u k’áabatal beyo’.
+
+ Oksa’ak le nuupul ti’ u kaxbil ba’al
+
+ Ila’al wa le nuupul ti’ u kaxbil ba’ale’ uts ti’ le paatul yáax e’esajilo’
+
+
+ Púustak le tsool ts’íibilo’
+
+
+ Ma’ ilbil
+
+
+ Lúubsal tuláakal tu\'ux maanech
+
+
+ U nu’ukbesaj ts’íibilo’ jeba’anil: %1$s
+
+
+ Utsil nuupankil
+
+
+ Chuupulil
+
+
+ Le tseelil webe’ ts’óok u chuupul
+
+
+ Jejelas tu’uxil
+
+
+ Xeen toj táanil
+
+
+ Ka’a chuupuk le tseelil webe’
+
+
+ Suutnen ti’ a maan ich web
+
+
+ P’áatak u chuupul le tseelil webe’
+
+
+ Suutnen ti’ le ba’il ta yáax ts’íibo’
+
+
+ .
+
+
+ Ma’ meyajta’al le kaxan ba’alo’
+
+ A tojbe’enilob
+
+ Je’ebak le nuupankil ich yaanal ba’ilo’
+
+ Je’el u beytal a jóok’oj ti’ %1$s uti’al a je’ik le nuupankila’ ich %2$s.
+
+ Kaxanta’ak junp’éel ba’il tu’ux ka pàajchajak u je’ik le nuupankila’
+
+ Mixjunp’éel le ba’il ich a núukulila’ u je’ik le nuupankila’. Je’el u beytal a jóok’oj ti’ %1$s uti’al a kaxtik junp’éel bail tu’ux u jèebej ich %2$s.
+
+ ¿A k’áat a jóok’oj ti’ le maan ich web chen a wóok’sal wa’a?
+
+
+ %1$s ts’óokij
+
+
+ Je\'ebek
+
+
+
+
+
+
+
+
+ Ma’ kaxta’ab le núukulil ku ts’a’ik internet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nojochkúuns a ba’il ta wóok’sal
+
+ Bis táanxil tu’ux le a maansajil ich web yéetel u ba’il a wóok’sal. Ma’ cha’ik u chíikpajal le koonol ba’alobo’ wa u láak’ ba’il ku kaxtikech ich le tseel web tu’ux ka maano’ uti’al u chaambelkúunsik a wéensik ba’alob te’ webo’.
+
+
+ A kaxan ba’al, je’e bey a k’áato’
+
+ ¿Ka kaxtik ba’al jela’anil wa’a? Yéey u jela’anil u múuk’il a kaxan ba’al ta yéeytaj yáax táanile’ ich le ba’ila’.
+
+
+ Ts’a’a u séeb k’at bejilo’ob ti’ a wokoj te’ yáax cha’anila’
+
+ Súutnen séebanil ti’ a tseel web uts ta wicho’ ich %1$s. Yéey \"ts’áabak ich u yáax tseel web\" lik’ul u ts’íibil tsoolanilo’ob %1$s.
+
+
+ Suuktatech u kanáantal le a wóok’salo’
+
+ Ts’a’a %1$s bey a kaxan ba’al a yéeymaj ka wilik le ma’alobil kaxan ba’al tu’ux u kanáantal le a wóok’sal ken a je’ik jejelas tseel web lik’ulil yaanal ba’ilob.
+
+ ¡Na’atbij!
+ Ma’ ilbij
+ U láak’o’
+
+
+ -
+
+
+ Ts’áabak
+
+
+ JE’ELE’
+
+
+ Ma’ u beetal
+
+
+ MA’
+
+
+ Meyajil ti’ maan ich web chen teech
+
+
+ Le ku ts’a’ik ojelbil ba’ax ku yúuchule’ ku cha’ik u túupul le meyajil %1$s chen yéetel jun péets’ak. Mix k’áabet a je’ik u ba’il wa mix k’áabet a wilik ba’ax ku meyaj ich tu’ux a maano’.
+
+
+ Lúubsal tuláakal tu\'ux maanech
+
+
+ Éemsa’ak Firefox
+
+
+
+
+
+
+
+
+ A k’áaba’
+ Wóojil
+ Púustak
+
+
+
+ Uts nuupankil
+ K’as nuupankil
+
+ Ila’abij men: %1$s
+
+
+ U jéets’elil te’e tu’uxilo’
+ Le URL yani’
+
+
+ %1$d/%2$d
+
+
+ Ma’
+
+
+ Je’ele’
+
+
+ Ma’ ilbil
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-zam/strings.xml b/mobile/android/focus-android/app/src/main/res/values-zam/strings.xml
new file mode 100644
index 0000000000..8fb42c387f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-zam/strings.xml
@@ -0,0 +1,443 @@
+
+
+
+
+
+
+
+
+ B-láɁ=y
+
+ ăɁ
+
+ Mb-lòɁ sóg
+
+
+ KwàɁn kó wiɁ
+
+ Wí lút tá. KwàɁn. Tedónt. Bí-ré.
+
+
+ Mté dont na ré ko wíl.
+
+ Ndó bíy
+
+
+ Ndó bíy sá.
+
+
+ KwàɁn lô %1$s
+
+
+ Tă~làz…
+
+
+ Gús KwàɁn m-xâc
+
+
+ Mb-šàlɁ leɁn %1$s
+
+
+ Mb-šàlɁ leɁn…
+
+
+ M-tòb lô yíʔb
+
+
+ M-tóba ló kó tílta yúl
+
+ Mté dónt kúe
+
+ Thús kùey
+ Chò nàk ná
+ Lì ché
+ Ko nákin niél
+
+
+ TòɁw-ná lô men dè-kè
+
+
+ ChaɁn %1$s
+
+
+ Tă~làz leɁn
+
+
+ Tób bí lô góš
+
+
+ Mb-šàlɁ lá
+
+
+ Tòb bí ner mb-šàlɁ
+
+
+ Tòb bì
+
+
+ Tób bí lô góš
+
+
+
+ Tòb bí & mb-šàlɁ
+
+
+ Tòb bí ner mb-šàlɁ %1$s
+
+
+ KwàɁn léɁn Focus
+
+ KwàɁn léɁn Klar
+
+ KwàɁn léɁn Focus Beta
+
+
+ KwàɁn léɁn Focus Nightly
+
+
+ Yent-chó wìʔ
+
+
+ Nêd, cookies ner yêts
+
+
+
+
+ KwàɁn %1$s, lì-chél
+
+
+ KwàɁn šò=leɁn
+
+
+ Sè App
+
+
+ Ré=tá
+
+ KwàɁn
+
+
+ b-šèn=ná
+
+
+ Blá Kìy
+
+
+ Tóg gá
+
+
+ URL mkéy
+
+
+ m-tób tùs kùe URL
+
+
+ M-tób tùs kùe URL
+
+
+ M-tòb URL
+
+
+ Cookies ner kwàɁn mb-šèn=lû
+
+
+ B-šèn=ná kùe
+
+
+ Mkìb tùs kùe URLs
+
+
+ Lab kùe
+
+
+ M-tòb URL
+
+
+ Lí chop ó m-tòb URL
+
+
+ "VáɁ: mozilla.org "
+
+
+ M-tòb: sálépágina.com
+
+
+ Dób lá URL kùb.
+
+
+ Kìb
+
+
+ Kìb
+
+
+ Wì cho besta blüy URL.
+
+ Dìzh
+
+ Kó díed lenɁ
+
+ Ngyé-n cho wiɁ
+ TòɁw-ná lô men dè-kè
+ TòɁw-ná lô men dè-kè
+ TòɁw-ná lô men dè-kè
+ TòɁw-ná lô men dè-kè
+ TòɁw cookies
+
+
+ Yénta
+ TòɁw 3 cookies ná táɁ thîb mèn
+
+ ăɁ líy
+
+
+ Yìz
+
+ Sà dè kèy sín
+ TòɁw letr Internet
+
+ TòɁw lô JavaScript
+
+
+ "M-tòb %1$s mdó-lás=lû "
+
+ Mozilla
+ M-tàl kò lì-chèl
+
+
+ Lab kùe
+
+
+ M-bláb kùe
+
+
+ Cho nak kùe %1$s
+
+
+ Tob yìb kwàɁn
+
+
+ bí~rè sá nák ahɁ
+
+
+ M-tòb tá thîb yìb kwàɁn
+ Kìb yíb kúan
+ Kíb
+
+
+ M-tòb yíʔb-kwàɁn
+
+ KwàɁn sá lé yíʔb-kwàɁn
+ KwàɁn nêt lí chèl
+ Mb-lòɁ sóg
+
+
+ Wí: lemen.com/search/?q=%s
+
+ Dób lá yíʔb-kwàɁn.
+
+ KwàɁn sá lé yíʔb-kwàɁn
+
+ KwàɁn sá lé dìzh
+
+
+ Tòmbî tòlô
+
+
+ B-láɁ=y
+
+
+ Tób bí lô góš
+
+
+ Lô nó-šàlɁ: %1$s
+
+
+ Mgéy sín
+
+
+ Mdé láɁ ní
+
+
+ Lô nó-šàlɁ
+
+
+ Wì kó ták lìl
+
+
+ Gûa sìs nêd
+
+
+ TeɁ zàp lô
+
+
+ Bì-rè nêd
+
+
+ B-láɁ mdé láɁ=y
+
+
+ Bí-ré app
+
+
+ TòɁw-ná lô men dè-kè
+
+ Ko nákin niél
+
+ Mb-šàlɁ nêt (n̆eza) lenɁ tá thîb app
+
+ Tàk rûl trè %1$s zá tàk mb-šàlɁ nêd ré lenɁ %2$s.
+
+ KwàɁn app kó tàk mb-šàlɁ nêd ré
+
+ Niè thîb app tàk mb-šàlɁ nêd ré. Tàk rûl trè %1$s zá tàk mb-šàlɁ nêd ré lenɁ %2$s.
+
+ ¿Mb-ròɁ gá?
+
+
+ %1$s tèlôy
+
+
+ Mb-šàlɁ lá
+
+
+
+
+
+
+
+ Ngyé-n yìb rè
+
+
+
+
+
+
+
+
+
+
+
+
+
+ M-tób kì ko lùt ta wí
+
+ Lleva la navegación privada a otro nivel. Bloquea anuncios publicitarios y otro tipo de contenido que puede rastrearte a través de los sitios y ralentizar el tiempo de descarga de las páginas.
+
+
+ Kò kwàʔn lû, nêb lû
+
+ ¿Buscas algo diferente? Elige otro motor de búsqueda predeterminado en Ajustes.
+
+
+ Agrega atajos a la pantalla principal
+
+
+ Acostúmbrate a la privacidad
+
+ ăɁ, gùa!
+ B-láɁ=y
+ Tá thîb vá
+
+
+ -
+
+
+ M-tòb
+
+
+ ăɁ
+
+
+ B-láɁ=y
+
+
+ Lút ta níe kúe
+
+
+ Tób bí lô góš
+
+
+ B-là Firefox
+
+
+ Chó lèl
+ Contraseñ
+ Tòb-bì
+
+
+
+ Bès-tá m-dób kîy
+ WíɁ bès-tá
+
+ WíɁ=y: %1$s
+
+
+ Zá nî-éy
+ URL šò=là
+
+
+ ăɁ
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-zh-rCN/strings.xml b/mobile/android/focus-android/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000000..a2fbdb9796
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,1127 @@
+
+
+
+
+
+
+
+
+ 取消
+
+ 确定
+
+ 保存
+
+
+ 搜索或输入网址
+
+ 全自动的私密浏览。\n无痕浏览,不必劳心。
+
+
+ 您的浏览历史已清除。
+ 浏览历史已清除
+
+
+ 已清除标签页浏览历史。
+
+
+ 搜索 %1$s
+
+
+ 分享…
+
+
+ 反馈网站问题
+
+
+ 用 %1$s 打开
+
+
+ 打开于…
+
+
+ 添加到主屏幕
+
+
+ 添加快捷方式
+
+ 移除快捷方式
+
+
+ 设置
+ 关于
+ 帮助
+ 您的权利
+
+
+ 已屏蔽跟踪器
+
+
+ 关闭此功能或许可以解决一些网站的问题
+
+
+ 内容拦截
+
+ 关闭以解决一些网站问题
+
+
+ 由 %1$s 提供
+
+
+ 分享方式
+
+ 需要清除浏览历史吗?
+ 点按或清除此通知,即可安全清除浏览历史。
+
+
+ 点按或轻扫此通知,即可安全清除浏览历史。
+
+ 清除浏览历史
+
+
+ 打开
+
+
+ 清除并打开
+
+
+ 清除
+
+
+ 清除浏览历史
+
+
+
+ 清除并打开
+
+
+ 清除并打开 %1$s
+
+
+
+ Focus · 搜索
+
+ Klar · 搜索
+
+ Focus Beta · 搜索
+
+ Focus Nightly · 搜索
+
+
+ %1$s 让您掌控线上生活。
+可以用作隐私浏览器:
+
+ 直接在应用中搜索和浏览
+ 拦截跟踪器(也可调整为允许跟踪器)
+ 自动清除 Cookie、搜索记录和浏览历史
+
+
+%1$s 由 Mozilla 出品。我们的使命是促成健康、开放的互联网。
+详细了解
]]>
+
+
+ 隐私与安全
+
+
+ 跟踪、Cookie、数据反馈
+
+
+ 设置默认引擎、自动补全
+
+
+
+
+ 关于 %1$s、使用说明
+
+
+ 增强型跟踪保护
+
+
+ 网络内容
+
+
+ 切换应用程序
+
+
+ 常规
+
+
+ 默认浏览器、语言
+
+
+ 数据收集与使用
+
+ 搜索
+
+
+ 获取搜索建议
+
+ %1$s 会将您在地址栏中输入的内容发送至您的搜索引擎
+
+
+ 默认
+
+
+ 搜索引擎
+
+
+ 开启
+
+
+ 关闭
+
+
+ 网址自动补全
+
+
+ 对于常用网站
+
+
+ 开启之后,%s 地址栏可以自动补全超过 450 个热门网站。
+
+
+ 对于您添加的网站
+
+
+ 开启之后,%s 可以自动补全您喜爱的网址。
+
+
+ 管理网站
+
+
+ 管理网站
+
+
+ + 添加自定义网址
+
+
+ 您的自动补全列表:
+
+
+ 添加网址
+
+
+ 添加自定义网址
+
+
+ 添加自定义网址
+
+
+ 添加自动补全的链接
+
+
+ Cookie 和网站数据
+
+
+ 数据反馈
+
+
+ 移除自定义网址
+
+
+ 详细了解
+
+
+ 添加和管理自定义的自动补全网址。
+
+
+ 要添加的网址
+
+
+ 粘贴或输入网址
+
+
+ 示例:mozilla.org
+
+
+ 例如:example.com
+
+
+ 已添加新的自定义网址。
+
+
+ 移除
+
+
+ 移除
+
+
+ 请复查您输入的网址。
+
+ 语言
+
+ 系统默认
+
+ 隐私
+ 拦截广告跟踪器
+ 一些广告在跟踪您的访问,即使您没有点击广告
+ 拦截分析跟踪器
+ 它们会收集、分析和测量您的活动,例如点按和滚动
+ 拦截社交跟踪器
+ 嵌入在网站上来跟踪您的访问,并显示分享按钮等功能
+ 拦截其他内容跟踪器
+ 启用可能导致部分网站运作不正常
+ 阻止 Cookie
+
+
+ 不用,谢谢
+ 仅阻止第三方跟踪器 Cookie
+ 仅阻止第三方 Cookie
+
+ 拦截跨站 Cookie
+ 好的
+
+
+ 使用指纹解锁应用
+
+
+ 若 %s 有打开的网站或添加过快捷方式,请使用指纹解锁。
+
+
+ 隐形
+
+ 切换应用时隐藏网页内容,并阻止截屏、投屏。
+
+ 安全
+
+ 性能
+ 拦截网络字体
+
+ 可能造成图标或图像缺失
+
+ 阻止 JavaScript
+
+ 页面加载可能更快,但可能遭遇意外状况
+
+
+ 将 %1$s 设为默认浏览器
+
+ Mozilla
+ 发送使用统计
+
+
+ 详细了解
+
+
+ Mozilla 坚持仅收集为大众提供和改进 %1$s 所必需的数据。
+
+
+ 隐私声明
+
+
+ 许可信息
+
+
+ 我们使用的开源库
+
+
+ %s | 开源软件库
+
+
+ 关于 %1$s
+
+
+ 已安装的搜索引擎
+
+
+ 选择搜索引擎
+
+
+ 恢复默认搜索引擎
+
+
+ 添加更多搜索引擎
+ 移除搜索引擎
+ 移除
+
+
+ 添加其他搜索引擎
+
+ 选择想使用的搜索引擎:
+
+
+ 添加搜索引擎
+
+ 搜索引擎名称
+ 搜索字符串
+ 保存
+
+
+ 例如:example.com/search/?q=%s
+
+ 已添加新的搜索引擎。
+
+ 输入搜索引擎名称
+ 一个已安装的搜索引擎已在使用该名称。
+
+ 输入搜索字符串
+
+ 请检查搜索字符串与示例格式是否匹配
+
+
+ 清空输入内容
+
+
+ 关闭
+
+
+ 清除浏览历史
+
+
+ 已打开 %1$s 个标签页
+
+
+ 安全连接
+
+
+ 正在加载
+
+
+ 网站已加载
+
+
+ 更多选项
+
+
+ 更多选项按钮
+
+
+ 浏览下一页
+
+
+ 重新加载网站
+
+
+ 浏览上一页
+
+
+ 停止加载网站
+
+
+ 返回之前的应用
+
+
+ 拦截的跟踪器数量
+
+
+ 拦截跟踪器
+
+ 您的权利
+
+ 用另一个应用打开链接
+
+ 您可以离开 %1$s,用 %2$s 打开此链接。
+
+ 寻找可以打开此链接的应用
+
+ 您的设备上的应用都不能打开此链接。您可以离开 %1$s,到 %2$s 找找适合的应用。
+
+ 退出隐私浏览模式?
+
+
+ %1$s 下载完毕
+
+
+ 打开
+
+
+
+
+
+
+
+
+
+
+ 快捷方式已添加!
+
+ 找不到服务器
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+
+
+ 欢迎使用 %1$s
+
+
+ 快速,隐私,高效率。
+
+
+ 开始使用
+
+
+
+ %1$s 与其他浏览器不同
+
+
+ 隐私保护考量,我们会在您关闭应用程序时,自动清除历史记录。
+
+
+
+ 将 %1$s 设为默认浏览器,保护您的数据安全。
+
+
+ 设为默认浏览器
+
+
+ 跳过
+
+
+
+ 强化您的隐私
+
+ 隐私浏览模式变得更强大了。能拦截广告,还能拦截在不同网站跟踪您,又拖慢网页加载的内容。
+
+
+ 用你的方式搜你所寻
+
+ 想搜点不一样的东西?可在设置里选择换个默认搜索引擎。
+
+
+ 在主屏幕中添加快捷方式
+
+ 快速用 %1$s 回到您喜爱的网站。只需在 %1$s 菜单中选择“添加到主屏幕”。
+
+
+ 让隐私成为一种习惯
+
+ 将 %1$s 设为您的默认浏览器,用其他应用打开网页时即可享受隐私浏览。
+
+ 明白了!
+ 跳过
+ 下一步
+
+
+ -
+
+
+ 添加
+
+
+ 是
+
+
+ 取消
+
+
+ 否
+
+
+ 使用此快捷方式打开时,将禁用跟踪保护功能
+
+
+ 隐私浏览会话
+
+
+ 推送通知可让您一键清除 %1$s 浏览状态。这样就无需事先打开应用,同时可以避免显露出浏览器中浏览的内容。
+
+
+ 清除浏览历史
+
+
+ 下载 Firefox
+
+
+
+
+
+
+
+
+ Mozilla 公共许可证及其他一些开源许可证向您授权。]]>
+
+
+ 此处。]]>
+
+
+ 许可证开放。]]>
+
+
+ GNU 通用公共许可证 v3 提供的一个独立的作品,可在这里 找到。]]>
+
+
+ 用户名
+ 密码
+ 清除
+
+
+
+ 安全连接
+ 不安全连接
+
+ 验证机构:%1$s
+
+
+ 网站安全性
+ 网址已存在
+
+
+ 在页面中查找
+
+
+ 在此页面中查找
+
+
+ %1$d/%2$d
+
+ 第 %1$d 项,共 %2$d 个匹配项
+
+
+ 查找下一个结果
+
+ 查找上一个结果
+
+ 关闭页内查找
+
+
+
+
+ 要求桌面版网站
+
+
+ 桌面版网站
+
+
+ 已复制网址
+
+
+ 开发者工具
+
+
+ 用外部应用打开链接
+
+
+ 高级
+
+
+ 网站权限
+
+
+ 减少 Cookie 横幅
+
+
+ 开启
+
+
+ 关闭
+
+
+ 减少 Cookie 横幅
+
+
+ 尽可能自动拒绝 Cookie 请求,减少横幅出现频率。
+
+ -->
+ 减少 Cookie 横幅
+
+
+ 在该网站开启
+
+
+ 暂不支持该网站
+
+
+ 在该网站关闭
+
+
+ 减少 Cookie 横幅
+
+
+ 在该网站关闭
+
+
+ 在该网站开启
+
+
+ 要为 %1$s 开启“减少 Cookie 横幅”功能吗?
+
+
+ 要为 %1$s 关闭“减少 Cookie 横幅”功能吗?
+
+
+ %1$s 将清除此网站的 Cookie 并刷新页面。清除 Cookie 可能会导致您退出登录,或清空购物车。
+
+
+ %1$s 会尝试自动拒绝 Cookie 请求。
+
+
+ “减少 Cookie 横幅”功能暂不支持此网站。您希望请求我们的团队调查此网站以在将来完成支持吗?
+
+
+ 取消
+
+
+ 请求支持
+
+
+ 请求已提交。
+
+
+ 请求已提交。
+
+
+
+ %1$s 会尝试拒绝 Cookie 请求以消除恼人的 Cookie 横幅。\n\n您可以在 %2$s 中管理 Cookie 横幅的首选项。
+
+
+ 设置
+
+
+ 自动播放
+
+
+ 若要允许:
+
+
+ 1. 前往 Android 设置
+
+
+ 权限]]>
+
+
+ 前往设置
+
+
+ %1$s权限]]>
+
+
+ 相机
+
+
+ 麦克风
+
+
+ 位置
+
+
+ 通知
+
+
+ 受 DRM 控制的内容
+
+
+ 始终询问
+
+
+ 阻止
+
+
+ 允许
+
+
+ 被 Android 阻止
+
+
+ 允许音频和视频
+
+
+ 仅阻止音频
+
+
+ 推荐
+
+
+ 阻止音频和视频
+
+
+ 研究
+
+
+ Firefox 可能会不时地安装并运行一些研究项目。
+
+
+ 详细了解
+
+
+ 应用程序将退出以应用更改
+
+
+ 移除
+
+
+ 进行中
+
+
+ 已完成
+
+
+ 通过 USB/Wi-Fi 远程调试
+
+
+ 解锁
+
+
+ 指纹确认
+
+
+ 你可以验证指纹以继续当前应用会话。
+
+
+ 新建会话打开链接
+
+
+ 指纹图标
+
+
+ 指纹无法识别。请重试。
+
+
+ 手指移动太快。请重试。
+
+
+ 要显示搜索建议吗?
+
+
+ 为获取建议,%1$s 需将您在地址栏中输入的内容发送至搜索引擎。
+
+
+ 否
+
+
+ 是
+
+
+ 某些搜索引擎无法显示建议。
+
+
+ 关闭
+
+
+
+
+ 网站表现异常??\n
+ 尝试关闭跟踪保护
+
+
+ 添加到主屏幕]]>
+
+
+ 用 %1$s 打开所有链接\n
+ 将 %1$s 设为默认浏览器
+
+
+
+ 自动补全您的常用网站地址\n
+ 长按地址栏的中任一链接
+
+
+
+ 新建标签页打开链接\n
+ 长按页面上的任一链接
+
+
+ 关闭启动页上的小提示
+
+
+ 已打开新标签页
+
+
+ 切换
+
+
+ 进入全屏模式
+
+
+ 立即显示新建打开的链接
+
+
+ 拦截潜在的危险与诈骗网站
+
+ 拦截已被报告的诈骗、攻击性,或含有恶意软件、垃圾软件的网站。
+
+
+ HTTPS-Only 模式
+
+
+ 自动尝试使用 HTTPS 加密协议连接至网站,以增强安全性。
+
+
+ 例外
+
+ 您已对这些网站禁用内容拦截。
+
+ 移除
+
+ 移除全部网站
+
+
+ 拦截 Cookie
+
+
+ 您是要拦截网站 Cookie 吗?
+
+
+ 标签页崩溃
+
+ 抱歉, 此标签页出现问题并已崩溃。
+
+ 作为一款隐私浏览器,我们不会保存浏览历史,因此无法还原此标签页。
+
+ 关闭标签页
+
+
+
+
+
+ 向 Mozilla 发送崩溃报告
+
+
+
+
+ 自%s起拦截的跟踪器
+
+ 内容
+
+ 营销
+
+ 社交
+
+ 统计
+
+ 增强型跟踪保护
+
+ 已关闭此网站的跟踪保护
+
+ 已开启此网站的跟踪保护
+
+
+ 连接安全
+
+ 连接不安全
+
+ 要拦截的跟踪器和脚本
+
+
+ 返回
+
+
+
+ 移除
+
+
+ 重命名
+
+ 重命名
+
+
+ 快捷方式名称
+
+
+ 清除 %1$s 的历史记录时,<b>将不会</b>删除保存和分享的图像。
+
+
+
+ 主题
+
+ 浅色
+
+ 深色
+
+ 依系统省电模式设置
+
+ 跟随设备主题
+
+
+
+ 此站点不支持 HTTPS
+
+
+ 了解更多
+ 通过“设置”>“隐私与安全”>“安全”更改此设置。]]>
+
+
+ 连接不安全
+
+
+
+ 如果您过去曾成功连接到此服务器,则该错误可能是暂时的。
+ ]]>
+
+
+ 可能有人试图冒充该网站,继续访问可能会有风险。
+
+ %1$s 不信任 %2$s ,因为其证书颁发者未知、证书为自签名的,或者服务器未发送正确的中间证书。
+ ]]>
+
+
+
+ 关闭标签页
+
+
+
+ 抓到了!我们已阻止此网站的跟踪,您可随时点按盾牌了解已拦截哪些内容。
+
+
+ 关闭弹窗
+
+
+
+ 保护您是我的首要职责
+
+ 默认设置已提供足够强大的保护。如您有特殊需求,也可方便地调整这些设置。
+
+ 知道了
+
+
+ 点按此处即可删除浏览历史、Cookie 等所有数据,并新建一个全新标签页。
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ 关闭
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ 搜索小组件
+
+ !-- This is the title of promote search widget dialog. -->
+
+ 浏览历史已清除!🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 进入隐私浏览模式,我们会在您上网时拦截跟踪器和其他脏东西。
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 开始隐私浏览吧。想更快发起搜索,下次可以直接点按主屏幕上的 %1$s 小组件。
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ 将小组件添加至主屏幕
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ 小组件已添加至主屏幕
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-zh-rHK/strings.xml b/mobile/android/focus-android/app/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000000..5f5f49a7e9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,933 @@
+
+
+
+
+
+
+
+
+ 取消
+
+ OK
+
+ 儲存
+
+
+ 搜尋或輸入地址
+
+ 自動私隱瀏覽\n睇完 · 刪咗佢 · 再嚟過
+
+
+ 你嘅瀏覽歷史已被刪除。
+
+ 已清除瀏覽歷史
+
+
+ Tab 嘅瀏覽歷史記錄已被刪除。
+
+
+ 搜索%1$s
+
+
+ 分享…
+
+
+ 報告網站問題
+
+
+ 喺%1$s中打開
+
+
+ 用…打開
+
+
+ 加入主螢幕
+
+
+ 加入捷徑
+
+ 從捷徑中移除
+
+
+ 設定
+ 關於
+ 說明
+ 你嘅權利
+
+
+ 已阻止追蹤器
+
+
+ 關閉呢個功能可能會修復一啲網站問題
+
+
+ 攔截內容
+
+
+ 關閉以修復某啲網站嘅問題
+
+
+ 由%1$s提供支持
+
+
+ 分享去
+
+
+ 刪除瀏覽紀錄
+
+
+ 打開
+
+
+
+ 刪除並開啟
+
+
+ 刪除
+
+
+ 刪除瀏覽記錄
+
+
+
+ 刪除並開啟
+
+
+ 刪除並開啟 %1$s
+
+
+ 喺 Focus 度搜尋
+
+ 喺 Klar 度搜尋
+
+ 喺 Focus Beta 度搜尋
+
+ 喺 Focus Nightly 度搜尋
+
+
+ 私隱同安全
+
+
+ 追蹤器、Cookies、數據選擇
+
+
+ 設定為預設,自動完成
+
+
+
+
+ 關於 %1$s、說明
+
+
+ 加強追蹤保護
+
+
+ 網頁內容
+
+
+ 切換應用程式
+
+
+ 一般
+
+
+ 預設瀏覽器、語言
+
+
+ 數據收集同使用
+
+ 搜尋
+
+
+ 獲得搜尋建議
+
+
+ 預設值
+
+
+ 搜尋引擎
+
+
+ 開咗
+
+
+ 冇開
+
+
+ 自動完成網址
+
+
+ 熱門網站
+
+
+ 你加入嘅網站
+
+
+ 管理網站
+
+
+ 管理網站
+
+
+ + 加入自訂網頁
+
+
+ 你嘅自動完成列表:
+
+
+ 加入網址
+
+
+ 加入自訂網址
+
+
+ 加入自訂網址
+
+
+ 加入自動完成嘅連結
+
+
+ Cookies 同網站數據
+
+
+ 數據選擇
+
+
+ 移除自訂網址
+
+
+ 想知多啲
+
+
+ 加入並管理自訂自動完成嘅URLs。
+
+
+ 加入嘅網址
+
+
+ 貼上或輸入網址
+
+
+ 例如:mozilla.org
+
+
+ 例如:example.com
+
+
+ 已加入新嘅自訂網址。
+
+
+ 移除
+
+
+ 移除
+
+
+ 請檢查多次你輸入嘅網址。
+
+ 語言
+
+ 系統預設值
+
+ 私隱
+ 阻擋廣告追蹤器
+ 就算你冇撳過啲廣告,有啲廣告都會跟蹤你嘅瀏覽
+ 阻擋分析追蹤器
+ 用嚟收集、分析並測量點擊同捲動之類嘅活動
+ 阻擋社交媒體追蹤器
+
+ 阻擋其他內容追蹤器
+ 開咗佢可能會導致某啲頁面出現異常行為
+ 阻擋 Cookies
+
+
+ 唔使喇,唔該
+ 淨係阻擋第三方追蹤器 Cookies
+ 淨係阻擋第三方 Cookies
+ 阻擋橫跨網站嘅 Cookies
+ 好吖,唔該晒
+
+
+ 用指紋去解鎖個 app
+
+
+ 隱形
+
+ 切換 app 嘅時候隱藏網頁並阻擋截圖。
+
+ 保密
+
+ 效能
+ 阻擋網絡字型
+
+
+ 或會導致遺失圖標或圖片
+
+ 阻擋 JavaScript
+
+
+ 網頁可能會載入得快啲,但亦可能出現問題
+
+
+ 將 %1$s 設定為預設瀏覽器
+
+ Mozilla
+ 傳送使用狀況數據
+
+
+ 想知多啲
+
+
+ Mozilla 致力只收集大家足以提供並改進 %1$s所需嘅資料。
+
+
+ 私隱權告示
+
+
+ 關於 %1$s
+
+
+ 已安裝嘅搜尋引擎
+
+
+ 揀選搜尋引擎
+
+
+ 還原預設嘅搜尋引擎
+
+
+ + 加入另一個搜尋引擎
+ 移除搜尋引擎
+ 移除
+
+ 加入另一個搜尋引擎
+
+
+ 揀選你鍾意嘅引擎:
+
+
+ 加入搜尋引擎
+
+ 搜尋引擎名稱
+ 要用嘅搜尋字串
+ 儲存
+
+
+ 例如:example.com/search/?q=%s
+
+ 加入新咗嘅搜尋引擎。
+
+ 輸入搜尋引擎名稱
+ 有一個已安裝嘅搜尋引擎已經用咗呢個名。
+
+ 輸入搜尋字串
+
+
+ 清除輸入
+
+
+ 收到
+
+
+ 刪除瀏覽紀錄
+
+
+ 開咗嘅分頁:%1$s 個
+
+
+ 安全連線
+
+
+ 載入緊
+
+
+ 載入咗網站
+
+
+ 更多選項
+
+
+ 更多選項掣
+
+
+ 前往下一頁
+
+
+ 重新整理網站
+
+
+ 返去上一頁
+
+
+ 停止載入網站
+
+
+ 返去先前嗰個 app
+
+
+ 已阻擋嘅追蹤器數目
+
+
+ 阻擋追蹤器
+
+ 你嘅權利
+
+ 用另一個 app 開啟連結
+
+ 你可以跳出 %1$s 以 %2$s 開啟呢個連結。
+
+ 搵個可以開啟連結嘅 app
+
+ 係咪要離開私隱瀏覽?
+
+
+ %1$s 已完成
+
+
+ 開啟
+
+ 搵唔到伺服器
+
+
+ 關閉
+
+
+
+ 歡迎使用 %1$s
+
+
+ 快速、私隱、簡潔。
+
+
+ 開始使用
+
+
+
+ %1$s 同其他瀏覽器唔同
+
+
+ 為咗更加保護你嘅私隱,我哋會在您閂咗個app時,自動清除瀏覽紀錄。
+
+
+
+ 將 %1$s 設為預設瀏覽器,保護你嘅私隱同資料。
+
+
+ 設為預設瀏覽器
+
+
+ 略過
+
+
+ 升級你嘅個人私隱
+
+
+ 你想點搜尋,就點搜尋
+
+
+ 想搜尋吓啲唔同嘅嘢?喺設定度揀選另一個預設嘅搜尋引擎啦。
+
+
+ 喺你嘅主頁加入捷徑
+
+
+ 私隱就習以為常
+
+
+ 將 %1$s 設定為你嘅預設瀏覽器,當你由其他 app 度開啟網頁嘅時候,都能夠得益於私隱瀏覽。
+
+ 明白晒!
+ 略過
+ 下一步
+
+
+ -
+
+
+ 加入
+
+ 好
+
+
+ 取消
+
+ 唔好
+
+
+ 捷徑會喺停用加強追蹤保護嘅情況下開啟
+
+
+ 私隱瀏覽工作階段
+
+
+ 刪除瀏覽紀錄
+
+
+ 下載 Firefox
+
+
+
+
+
+ 使用者名稱
+ 密碼
+ 清除
+
+
+
+ 安全連線
+ 不安全連線
+
+ 經 %1$s 驗證
+
+
+ 網站安全性
+ 已經有呢個網址
+
+
+ 喺呢頁搵嘢
+
+
+ 喺呢頁搵嘢
+
+
+ %1$d/%2$d
+
+ 第 %1$d 個,共 %2$d 個
+
+
+ 搵下一個結果
+
+ 搵上一個結果
+
+
+ 關閉頁面搜尋
+
+
+ 請求桌面版網頁
+
+
+ 桌面版
+
+
+ 已複製網址
+
+
+ 開發者工具
+
+
+ 以其他 apps 開啟連結
+
+
+ 進階
+
+
+ 網站權限
+
+
+ 減少 Cookie 橫額
+
+
+ 開啟
+
+
+ 關閉
+
+
+ 減少 Cookie 橫額
+
+
+ 盡可能自動拒絕 Cookie 請求,讓你看到更少橫額。
+
+ -->
+ 減少 Cookie 橫額
+
+
+ 於此網站開啟
+
+
+ 於此網站關閉
+
+
+ 減少 Cookie 橫額
+
+
+ 於此網站關閉
+
+
+ 於此網站開啟
+
+
+ 要為 %1$s 開啟 Cookie 橫額減少功能?
+
+
+ 要為 %1$s 關閉 Cookie 橫額減少功能?
+
+
+ %1$s 將清除此網站的 Cookie 並重新載入頁面。清除 Cookie 可能會將你從網站登出,或清空購物車。
+
+
+ 自動播放
+
+
+ 允許佢:
+
+
+ 1. 去 Android 設定
+
+
+ 權限]]>
+
+
+ 去設定
+
+
+ %1$s去開咗佢]]>
+
+
+ 相機
+
+
+ 咪高風
+
+
+ 位置
+
+
+ 通知
+
+
+ 由數碼權利管理嘅內容
+
+
+ 每次都問咗先
+
+
+ 封鎖咗嘅
+
+
+ 允許咗嘅
+
+
+ 由Android封鎖咗
+
+
+ 允許自動播放影音
+
+
+ 僅封鎖聲音
+
+
+ 推薦嘅嘢
+
+
+ 封鎖影音
+
+
+ 使用者研究
+
+
+ Firefox 可能周不時會安裝同展開研究。
+
+
+ 想知多啲
+
+
+ 程式將會結束以便套用更改
+
+
+ 移除
+
+
+ 生效
+
+
+ 已完成
+
+
+ 透過 USB/Wi-Fi 遙距除錯
+
+
+ 解鎖
+
+
+ 確認您嘅指紋
+
+
+ 使用指紋解鎖後,你可以繼續該工作階段。
+
+
+ 用一個新嘅工作階段開啟連結
+
+
+ 指紋圖標
+
+
+ 未能識別指紋。請再試一次。
+
+
+ 手指移動得太快。請再試一次。
+
+
+ 係咪要顯示搜尋建議?
+
+
+ 要獲得建議嘅話,%1$s 需要將你喺網址列度打嘅嘢傳送畀搜尋引擎。
+
+
+ 唔好
+
+
+ 好
+
+
+ 部份搜尋引擎冇建議提供。
+
+
+ 取消
+
+
+
+
+ 網站係咪出現問題?\n
+ 試下關閉追蹤保護
+
+
+ 加入去主畫面]]>
+
+
+ 以新 tab 開啟連結\n
+ 喺網頁長按任何連結
+
+
+
+ 關閉喺開始畫面上嘅貼士
+
+
+ 已開啟新嘅 tab
+
+
+ 切換
+
+
+ 立即切換去新 tab 度嘅連結
+
+
+ 阻擋潛在嘅危險同欺詐網站
+
+
+ 僅 HTTPS 模式
+
+
+ 自動只會連線到使用 HTTPS 協議加密嘅網站,以增加安全。
+
+
+ 例外
+
+ 你喺呢啲網站停用咗內容阻擋。
+
+ 移除
+
+ 移除所有網站
+
+
+ 阻擋 Cookies
+
+
+ 你想唔想阻擋Cookies?
+
+
+ Tab 冧檔
+
+ 對唔住。呢個 tab 發生問題。
+
+ 作為私隱瀏覽器,我哋無法儲存或還原呢個 tab。
+
+ 關閉 Tab
+
+
+ 傳送冧檔報告畀 Mozilla
+
+
+
+
+ 自 %s 起已阻擋嘅追蹤器數目
+
+ 內容
+
+ 廣告
+
+ 社交媒體
+
+ 數據分析
+
+ 加強追蹤保護
+
+
+ 呢個網站已關閉保護
+
+ 呢個網站已開啟保護
+
+ 安全連線
+
+ 不安全連線
+
+
+ 要阻擋嘅追蹤器同程式碼
+
+
+ 返回
+
+
+
+ 移除
+
+
+ 重新命名
+
+ 重新命名
+
+ 捷徑名稱
+
+
+
+ 色系
+
+ 淺色
+
+ 深色
+
+ 按省電模式決定
+
+
+ 跟返裝置嘅佈景主題
+
+
+
+ 關閉 tab
+
+
+
+ 我哋捉到同阻止咗呢個網站追蹤您。㯲下個盾牌嚟了解我哋阻止咗啲乜。
+
+
+ 閂咗啲彈出式窗口
+
+
+
+ 你已經受到保護!
+
+
+ 預設設定已經提供咗強大保護,但如果有需要,你可以簡單調整一下。
+
+ 閂咗呢舊嘢佢
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ 關閉
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ 搜尋小工具
+
+ !-- This is the title of promote search widget dialog. -->
+
+ 已清除瀏覽紀錄!
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 使用私人瀏覽模式,我哋就會同你擋曬啲追蹤器同其他衰嘢㗎喇。
+
+
+ 跟著就俾返你繼續私人瀏覽啦,但係想快速開始搜尋嘅話,可以把 %1$s 嘅搜尋小工具放到主畫面。
+
+
+ 將小工具添加到主介面
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ 已經將小工具添加至主介面
+
diff --git a/mobile/android/focus-android/app/src/main/res/values-zh-rTW/strings.xml b/mobile/android/focus-android/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000..544e8ddf9f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+ 取消
+
+ 確定
+
+ 儲存
+
+
+ 進行搜尋或輸入網址
+
+ 無痕瀏覽,不留足跡。
+
+
+ 已清除您的瀏覽紀錄。
+ 已清除瀏覽紀錄
+
+
+ 已清除分頁瀏覽紀錄。
+
+
+ 搜尋 %1$s
+
+
+ 分享…
+
+
+ 回報網站問題
+
+
+ 使用 %1$s 開啟
+
+
+ 開啟於…
+
+
+ 新增至裝置主畫面
+
+
+ 新增捷徑
+
+ 刪除捷徑
+
+
+ 選項
+ 關於
+ 說明
+ 您的權利
+
+
+ 追蹤器封鎖數量
+
+
+ 關閉此功能可能可以解決一些網站的問題
+
+
+ 內容封鎖
+
+ 關閉此功能來解決一些網站問題
+
+
+ 由 %1$s 提供
+
+
+ 透過下列程式分享
+
+ 要清除瀏覽紀錄嗎?
+ 點擊或滑掉這則通知,即可安全地清除您的瀏覽紀錄。
+
+
+ 點擊或滑掉這則通知,即可安全地清除您的瀏覽紀錄。
+
+ 清除瀏覽紀錄
+
+
+ 開啟
+
+
+ 清除並開啟
+
+
+ 清除
+
+
+ 清除瀏覽紀錄
+
+
+
+ 清除並開啟
+
+
+ 清除並開啟 %1$s
+
+
+
+ 使用 Focus 搜尋
+
+ 使用 Klar 搜尋
+
+ 使用 Focus Beta 搜尋
+
+ 使用 Focus Nightly 搜尋
+
+
+ %1$s 讓您可自行控制線上生活。
+可當作私密瀏覽器使用:
+
+ 直接在程式中搜尋、瀏覽
+ 封鎖追蹤器(也可調整設定,允許追蹤器)
+ 清除 Cookie、搜尋紀錄、上網紀錄
+
+
+%1$s 是由 Mozilla 所打造,我們的使命是培養一個健康、開放的網際網路環境。
+了解更多
]]>
+
+
+ 隱私與安全
+
+
+ 追蹤、Cookie、回報資料
+
+
+ 設為預設瀏覽器、自動完成
+
+
+
+
+ 關於 %1$s、使用說明
+
+
+ 加強型追蹤保護
+
+
+ 網站內容
+
+
+ 切換應用程式
+
+
+ 一般
+
+
+ 預設瀏覽器、語言
+
+
+ 資料收集與使用
+
+ 搜尋
+
+
+ 取得搜尋建議
+
+ 您在網址列打字時,%1$s 就會將您輸入的文字傳送到搜尋引擎
+
+
+ 預設
+
+
+ 搜尋引擎
+
+
+ 開啟
+
+
+ 關閉
+
+
+ 網址自動完成
+
+
+ 針對熱門網站
+
+
+ 開啟後,即可在 %s 網址列自動完成超過 450 組熱門網址。
+
+
+ 針對您加入的網站
+
+
+ 開啟後,即可讓 %s 自動完成您最愛的網址。
+
+
+ 管理網站
+
+
+ 管理網站
+
+
+ + 新增自訂網址
+
+
+ 您的自動完成清單:
+
+
+ 新增網址
+
+
+ 新增自訂網址
+
+
+ 新增自訂網址
+
+
+ 加入鏈結自動完成
+
+
+ Cookie 與網站資料
+
+
+ 回報資料
+
+
+ 移除自訂網址
+
+
+ 了解更多
+
+
+ 新增與管理自訂自動完成網址。
+
+
+ 要新增的網址
+
+
+ 貼上或輸入網址
+
+
+ 例如: mozilla.org
+
+
+ 舉例: example.com
+
+
+ 已新增自訂網址。
+
+
+ 移除
+
+
+ 移除
+
+
+ 請檢查您輸入的網址。
+
+ 語言
+
+ 系統預設
+
+ 隱私權
+ 封鎖廣告型追蹤器
+ 某些廣告就算您不點擊,也會追蹤您造訪的網站
+ 封鎖分析型追蹤器
+ 用來收集、分析、測量點擊與滾動等行為
+ 封鎖社交追蹤器
+ 內嵌於網站,用來追蹤您的造訪紀錄,並提供分享按鈕等功能
+ 封鎖其他內容追蹤器
+ 開啟後,可能會造成某些頁面的行為不正常
+ 封鎖 Cookie
+
+
+ 不要,謝謝
+ 僅封鎖第三方追蹤 Cookie
+ 僅封鎖第三方 Cookie
+
+ 封鎖跨網站 Cookie
+ 好!
+
+
+ 使用指紋解鎖程式
+
+
+ 若已用 %s 開啟網站或加入捷徑,可使用指紋直接解鎖。
+
+
+ 秘密行動
+
+ 切換程式時,隱藏網頁內容並防止拍攝擷圖。
+
+ 安全性
+
+ 效能
+ 封鎖網路字型
+
+ 可能會造成網頁當中的圖示或圖片消失
+
+ 封鎖 JavaScript
+
+ 頁面可能會更快載入,但也可能會造成功能不正常
+
+
+ 將 %1$s 設為預設瀏覽器
+
+ Mozilla
+ 傳送使用資料
+
+
+ 了解更多
+
+
+ Mozilla 致力於只收集用來為眾人改善 %1$s 所需的必要資料。
+
+
+ 隱私權公告
+
+
+ 授權資訊
+
+
+ 我們使用的程式庫
+
+
+ %s | 開放原始碼程式庫
+
+
+ 關於 %1$s
+
+
+ 已安裝的搜尋引擎
+
+
+ 選擇搜尋引擎
+
+
+ 還原預設搜尋引擎
+
+
+ + 新增其他搜尋引擎
+ 移除搜尋引擎
+ 移除
+
+
+ 新增其他搜尋引擎
+
+ 選擇想使用的搜尋引擎:
+
+
+ 新增搜尋引擎
+
+ 搜尋引擎名稱
+ 要使用的搜尋字串
+ 儲存
+
+
+ 例如: example.com/search/?q=%s
+
+ 已加入新搜尋引擎。
+
+ 輸入搜尋引擎名稱
+ 已有安裝的搜尋引擎使用該名稱。
+
+ 輸入搜尋字串
+
+ 請確認搜尋字串是否符合範例格式
+
+
+ 清除輸入內容
+
+
+ 結束輸入
+
+
+ 清除瀏覽紀錄
+
+
+ 開啟分頁數: %1$s
+
+
+ 安全連線
+
+
+ 載入中
+
+
+ 網站已載入
+
+
+ 更多選項
+
+
+ 更多選項按鈕
+
+
+ 瀏覽下一頁
+
+
+ 重新載入網站
+
+
+ 瀏覽上一頁
+
+
+ 停止載入網站
+
+
+ 回到先前的應用程式
+
+
+ 已封鎖的追蹤器數量
+
+
+ 封鎖追蹤器
+
+ 您的權利
+
+ 用其他應用程式開啟鏈結
+
+ 您可離開 %1$s,到 %2$s 安裝應用程式來開啟此鏈結。
+
+ 尋找可開啟此鏈結的應用程式
+
+ 您裝置中並未安裝可開啟此鏈結的應用程式。您可離開 %1$s,到 %2$s 安裝一套。
+
+ 要結束隱私瀏覽嗎?
+
+
+ %1$s 下載完成
+
+
+ 開啟
+
+
+
+
+
+
+
+
+
+
+ 已新增捷徑!
+
+ 找不到伺服器
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 關閉
+
+
+
+ 歡迎使用 %1$s
+
+
+ 快速、有隱私、不讓人分心。
+
+
+ 開始使用
+
+
+
+ %1$s 與其他瀏覽器不同
+
+
+ 為了加強保護隱私,我們會在您關閉應用程式時,自動清除瀏覽紀錄。
+
+
+
+ 將 %1$s 設為預設瀏覽器,保護您的資料。
+
+
+ 設為預設瀏覽器
+
+
+ 略過
+
+
+
+ 加強您的隱私
+
+ 隱私瀏覽功能變得更強大了。現在可封鎖廣告,以及其他可在不同網站間追蹤您的上網紀錄,又拖慢網頁載入的追蹤器。
+
+
+ 用你的方式搜尋
+
+ 想找點不一樣的東西嗎?可在設定畫面中選擇不同的預設搜尋引擎。
+
+
+ 在主畫面加入捷徑
+
+ 只要選擇 %1$s 選單中的「新增至裝置主畫面」即可快速用 %1$s 回到您最愛的網站。
+
+
+ 讓隱私成為一種習慣。
+
+ 將 %1$s 設為您的預設瀏覽器,從其他程式開啟網頁時即可直接進入隱私瀏覽模式從中受益。
+
+ 好,知道了!
+ 略過
+ 下一步
+
+
+ -
+
+
+ 新增
+
+
+ 好的
+
+
+ 取消
+
+
+ 不要
+
+
+ 使用此捷徑開啟時,將關閉追蹤保護功能
+
+
+ 隱私瀏覽階段
+
+
+ 不需要開啟程式或確認瀏覽器中有哪些資料,只要輕鬆一點,就能清除 %1$s 瀏覽階段的通知項目。
+
+
+ 清除瀏覽紀錄
+
+
+ 下載 Firefox
+
+
+
+
+
+
+
+
+ Mozilla Public License 以及其他開放原始碼授權條款提供給您。]]>
+
+
+ 這裡找到更多資訊。]]>
+
+
+ 授權條款提供。]]>
+
+
+ GNU General Public License v3 條款分別提供的獨立作品,可在此處 取得。]]>
+
+
+ 使用者名稱
+ 密碼
+ 清除
+
+
+
+ 安全連線
+ 不安全的連線
+
+ 驗證機構: %1$s
+
+
+ 網站安全性
+ 網址已經存在
+
+
+ 在頁面中搜尋
+
+
+ 在頁面中搜尋
+
+
+ %1$d/%2$d
+
+ 第 %1$d 筆,共 %2$d 筆
+
+
+ 尋找下一筆
+
+ 尋找上一筆
+
+ 關閉頁面搜尋
+
+
+
+
+ 開啟桌面版網站
+
+
+ 桌面版網站
+
+
+ 已複製網址
+
+
+ 開發者工具
+
+
+ 用 App 開啟鏈結
+
+
+ 進階
+
+
+ 網站權限
+
+
+ 減少 Cookie 橫幅
+
+
+ 開啟
+
+
+ 關閉
+
+
+ 減少 Cookie 橫幅
+
+
+ 盡可能自動拒絕 Cookie 請求,讓您能看到更少橫幅。
+
+ -->
+ 減少 Cookie 橫幅
+
+
+ 對此網站開啟
+
+
+ 目前不支援的網站
+
+
+ 對此網站關閉
+
+
+ 減少 Cookie 橫幅
+
+
+ 對此網站關閉
+
+
+ 對此網站開啟
+
+
+ 要為 %1$s 開啟減少 Cookie 橫幅功能嗎?
+
+
+ 要為 %1$s 關閉減少 Cookie 橫幅功能嗎?
+
+
+ %1$s 將清除此網站的 Cookie 並重新載入頁面。清除 Cookie 可能會將您從網站登出,或清空購物車。
+
+
+ %1$s 可以嘗試自動為您拒絕網站要設定 Cookie 的請求。
+
+
+ 「減少 Cookie 橫幅」功能目前暫不支援此網站。您想要將此網站回報給我們的團隊,看看是否未來有機會支援嗎?
+
+
+ 取消
+
+
+ 請求技術支援
+
+
+ 已送出對此網站的需求。
+
+
+ 已送出對此網站的需求。
+
+
+
+ %1$s 會嘗試拒絕網站設定 Cookie 的請求,來自動為您關閉擾人的 Cookie 橫幅。\n\n可到 %2$s 管理 Cookie 橫幅的行為。
+
+
+ 設定
+
+
+ 自動播放
+
+
+ 若要允許:
+
+
+ 1. 開啟 Android 設定
+
+
+ 權限]]>
+
+
+ 開啟設定
+
+
+ %1$s權限]]>
+
+
+ 相機
+
+
+ 麥克風
+
+
+ 位置
+
+
+ 通知
+
+
+ 由 DRM 控制的內容
+
+
+ 總是詢問
+
+
+ 封鎖
+
+
+ 允許
+
+
+ 被 Android 封鎖
+
+
+ 允許自動播放影音內容
+
+
+ 僅封鎖音訊
+
+
+ 推薦
+
+
+ 封鎖影音內容
+
+
+ 使用者研究
+
+
+ Firefox 會不定時安裝並執行使用者研究。
+
+
+ 了解更多
+
+
+ 應用程式將結束以套用變更
+
+
+ 移除
+
+
+ 進行中
+
+
+ 已完成
+
+
+ 透過 USB/Wi-Fi 進行遠端除錯
+
+
+ 解鎖
+
+
+ 使用您的指紋確認
+
+
+ 您可以使用指紋來繼續使用應用程式。
+
+
+ 用新瀏覽階段開啟鏈結
+
+
+ 指紋圖示
+
+
+ 無法識別指紋,請再試一次。
+
+
+ 手指移動得太快,請再試一次。
+
+
+ 要顯示搜尋建議嗎?
+
+
+ 為了取得建議,%1$s 需要將您在網址列輸入的文字傳送至搜尋引擎。
+
+
+ 不要
+
+
+ 好的
+
+
+ 某些搜尋引擎無法提供建議。
+
+
+ 知道了!
+
+
+
+
+ 網站變得怪怪的?\n
+ 試試關閉追蹤保護功能
+
+
+ 新增至裝置主畫面]]>
+
+
+ 使用 %1$s 開啟所有鏈結\n
+ 將 %1$s 設為預設瀏覽器
+
+
+
+ 自動完成您最常造訪的網站網址\n
+ 長按網址列中的任何網址
+
+
+
+ 使用新分頁開啟鏈結\n
+ 長按頁面上的任何鏈結
+
+
+
+ 關閉啟動畫面上的使用秘訣
+
+
+ 已開啟新分頁
+
+
+ 切換
+
+
+ 進入全螢幕模式
+
+
+ 開啟鏈結後,立即切換到該分頁
+
+
+ 封鎖可能危險或詐騙的網站
+
+ 封鎖已被回報為詐騙、有攻擊性、惡意軟體以及有害軟體的網站。
+
+
+ 純 HTTPS 模式
+
+
+ 自動嘗試使用加密過的 HTTPS 通訊協定連線到網站,以增加安全性。
+
+
+ 例外網站
+
+ 您已針對這些網站關閉內容封鎖功能。
+
+ 移除
+
+ 移除所有網站
+
+
+ 封鎖 Cookie
+
+
+ 您想要封鎖網站 Cookie 嗎?
+
+
+ 分頁發生錯誤
+
+ 很抱歉,這個分頁有點問題。
+
+ 作為一套隱私瀏覽器,我們不會保存上網紀錄,也無法還原此分頁。
+
+ 關閉分頁
+
+
+
+
+
+ 傳送錯誤報告給 Mozilla
+
+
+
+
+ 自 %s起封鎖的追蹤器數量
+
+ 內容
+
+ 廣告
+
+ 社交
+
+ 統計
+
+ 加強型追蹤保護
+
+ 已關閉此網站的追蹤保護
+
+ 已開啟此網站的追蹤保護
+
+ 連線安全
+
+ 連線不安全
+
+ 要封鎖的追蹤器與指令碼
+
+
+ 回上一頁
+
+
+
+ 移除
+
+
+ 重新命名
+
+ 重新命名
+
+
+ 捷徑名稱
+
+
+ 清除 %1$s 瀏覽紀錄時,<b>將不會</b>刪除已儲存或分享的圖片
+
+
+
+ 佈景主題
+
+ 亮色
+
+ 暗色
+
+ 由系統省電模式設定
+
+ 依照裝置佈景主題顯示
+
+
+
+ 此網站不支援 HTTPS
+
+
+ 了解更多 可到「設定 > 隱私權與安全性 > 安全性」調整此設定。]]>
+
+
+ 不安全連線
+
+
+
+若您以前可以與該伺服器正常連線,那麼這個錯誤可能只是暫時的,請稍候再試試看。]]>
+
+
+ 有心人士可能正在嘗試將別的網站偽裝成您想造訪的網站,若繼續開啟網站可能會有風險。
+
+因為憑證簽發者未知、憑證是自簽憑證,或伺服器並未送出正確的中介憑證的關係,%1$s 無法信任 %2$s 。 ]]>
+
+
+
+ 關閉分頁
+
+
+
+ 抓到了!我們已防止此網站追蹤您。隨時點擊盾牌即可了解我們封鎖了哪些內容。
+
+
+ 關閉彈出視窗
+
+
+
+ 讓我們保護您!
+
+ 下列預設設定就已經提供了強大保護,但如果您有特殊需求,也很簡單就能調整。
+
+ 知道了!
+
+
+ 點擊此處即可清除上網紀錄、Cookie 等所有資料,然後重新開啟一個乾乾淨淨的分頁。
+
+
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for the
+ close button from promote search widget dialog. -->
+
+ 關閉
+
+ !-- Content description (not visible, for screen readers etc.): This is the description for
+ picture of search widget from promote search widget dialog. -->
+
+ 搜尋小工具
+
+ !-- This is the title of promote search widget dialog. -->
+
+ 已清除瀏覽紀錄!🎉
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 進入隱私瀏覽模式,我們會在您上網時封鎖追蹤器與其他髒東西。
+
+ !-- This is the subtitle of promote search widget dialog. %1$s will get replaced with the name
+ of the app (e.g. "Focus") -->
+
+ 接下來就交給您繼續進行隱私瀏覽了。未來想快速開始搜尋的話,可以把 %1$s 搜尋小工具放到主畫面。
+
+ !-- This is te text from add search widget to home screen button .The button is located on
+ promote search widget dialog. -->
+
+ 將小工具新增至裝置主畫面
+
+ !-- This is te text of the snackbar that appears after the search widget was added successfully
+ to the home screen. -->
+
+ 已將小工具新增至裝置主畫面
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/app.xml b/mobile/android/focus-android/app/src/main/res/values/app.xml
new file mode 100644
index 0000000000..f5c9b95425
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/app.xml
@@ -0,0 +1,13 @@
+
+
+
+ Firefox Focus
+
+
+ Focus
+
+
+ Focus
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/attrs.xml b/mobile/android/focus-android/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000000..597f7f1ae6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/attrs.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/values/colors.xml b/mobile/android/focus-android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..a8b414e983
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/colors.xml
@@ -0,0 +1,152 @@
+
+
+ @color/photonViolet05
+ @color/photonLightGrey05
+ @color/photonInk50
+ @color/photonDarkGrey05
+
+ @color/photonBlack
+
+ @color/photonRed70
+ #e452B9
+
+ #66312A65
+ @color/colorPrimary
+ @color/photonViolet05
+
+ #80FFFFFF
+ @color/photonDarkGrey05
+ @color/background
+
+ #ccffffff
+ #cc000000
+
+ @color/photonLightGrey05
+
+ @color/photonLightGrey30
+ @color/photonPink70
+ @color/photonPink70
+ #FF6A75
+
+
+ #D4D5FA
+ #D3D5FB
+ #D5D7FA
+
+ #D3D4FA
+ #D7D6F9
+ #ECEAFA
+ #F5F4FE
+ #D9D3F5
+ #D6C9EB
+
+
+ #FFFF1AD9
+ #FFFF1AD9
+ #FFFF1AD9
+
+ @color/photonMagenta70
+ @color/photonMagenta80
+
+ @color/photonBlue40
+ @color/photonBlue40
+
+
+ @color/colorPrimary
+
+ #353852
+ @color/photonLightGrey05
+ @color/colorSecondary
+
+
+ @color/colorSecondary
+ @color/primaryText
+ @color/accentBright
+
+
+ @color/photonLightGrey05
+ @color/photonViolet05
+ @color/photonInk80
+ @color/photonInk80
+ @color/colorSecondary
+ @color/accentBright
+
+ #ffffffff
+ @color/photonLightGrey05
+ #480f1126
+ #ffbaa8e6
+
+
+ #7FFFFFFF
+ #FFFFFFFF
+ #312A64
+ #E0E0E6
+ #F2F2F7
+
+
+ @color/photonLightGrey05
+ @color/photonViolet05
+
+
+ @color/photonInk60
+
+ @color/focusContrastPink
+
+ #b2b2b2b2
+ #FF2AA1FE
+
+
+ @color/secondaryText
+ @color/photonLightGrey05
+ @color/accentBright
+ @color/accentBright
+
+ @color/photonGrey10
+ @color/accentBright
+
+ #353852
+ #B3313131
+
+ @color/background
+
+ @color/photonBlack
+
+
+ @color/photonDarkGrey05
+ @color/photonLightGrey05
+
+
+ @color/colorSecondary
+
+
+ @color/primaryText
+ @color/colorPrimary
+ @color/secondaryText
+
+
+ #FBFBFE
+ #AB71FF
+ #592ACB
+ #FFFFFF
+
+
+ #312A64
+
+
+ #D3D4FA
+ #D7D6F9
+ #ECEAFA
+ #F0EFFA
+ #D9D3F5
+ #D6C9EB
+
+
+ #FFFFFF
+ #F2F2F7
+ #312A64
+
+
+ @color/photonViolet70
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/configuration.xml b/mobile/android/focus-android/app/src/main/res/values/configuration.xml
new file mode 100644
index 0000000000..180caf0f80
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/configuration.xml
@@ -0,0 +1,14 @@
+
+
+
+ 250
+ 100
+ 100
+ 150
+
+ 500
+
+ 500
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/dimens.xml b/mobile/android/focus-android/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000000..61baa3e098
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/dimens.xml
@@ -0,0 +1,91 @@
+
+
+
+ 8dp
+
+ 40dp
+ 16dp
+ 16dp
+
+ - -1
+
+ @dimen/match_parent
+
+
+ 24dp
+
+
+ 0dp
+ 0dp
+ 0dp
+
+ 300dp
+ 460dp
+
+ 64dp
+ 48dp
+
+ 12dp
+
+ 108dp
+
+ 24dp
+ 65%
+ 75%
+
+ 16dp
+ 24dp
+ 32dp
+ 8dp
+ 16dp
+ 12dp
+ 48dp
+
+ 8dp
+
+ 57dp
+
+ 300dp
+ 250dp
+ 12dp
+ 67dp
+ 5dp
+ 8dp
+ 10dp
+ 20dp
+ 30dp
+ 60dp
+ 4dp
+ 16dp
+
+ 280dp
+
+ 88dp
+ 20dp
+ 46dp
+ 12dp
+
+ 56dp
+ 16dp
+
+ 56dp
+
+
+ 112dp
+ 314dp
+
+
+ 48dp
+ 48dp
+ 18dp
+ 16dp
+
+
+
+ @dimen/match_parent
+ 64dp
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/fonts.xml b/mobile/android/focus-android/app/src/main/res/values/fonts.xml
new file mode 100644
index 0000000000..95a56f16f9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/fonts.xml
@@ -0,0 +1,8 @@
+
+
+
+ sans-serif-medium
+ sans-serif
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/ids.xml b/mobile/android/focus-android/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000000..485dee349b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/ids.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/preference_keys.xml b/mobile/android/focus-android/app/src/main/res/values/preference_keys.xml
new file mode 100644
index 0000000000..6fbdb2dd5f
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/preference_keys.xml
@@ -0,0 +1,125 @@
+
+
+
+
+ pref_search_engine
+ pref_show_search_suggestions
+ pref_manual_add_search_engine
+ pref_radio_search_engine_list
+ pref_multiselect_search_engine_list
+
+ pref_key_remote_server_prod
+
+ pref_privacy_block_ads
+ pref_privacy_block_analytics
+ pref_privacy_block_social
+ pref_privacy_block_cryptomining
+ pref_privacy_block_fingerprinting
+ pref_privacy_block_other3
+ pref_key_privacy_total_trackers_blocked_count
+ pref_key_privacy_ever_changed_etp
+
+ pref_key_tool_tip
+
+ pref_performance_block_webfonts
+ pref_performance_block_javascript
+ pref_performance_enable_cookies
+ pref_performance_block_images
+
+ pref_default_browser
+ pref_key_leakcanary
+
+ pref_telemetry
+
+ pref_secure
+
+ pref_safe_browsing
+
+ pref_https_only
+
+ pref_biometric
+ pref_about
+ pref_help
+ pref_rights
+ pref_privacy_notice
+ pref_licensing_info
+ pref_libraries_we_use
+
+ pref_locale
+ pref_in_app_review_openings
+ pref_in_app_review_step
+ pref_in_app_review_time
+
+ pref_screen_autocomplete
+ pref_autocomplete_preinstalled
+ pref_autocomplete_custom
+ pref_screen_custom_domains
+
+ pref_screen_privacy_security
+ pref_advanced_screen
+ pref_general_screen
+
+ pref_screen_mozilla
+ pref_screen_search
+ pref_remote_debugging
+ pref_key_open_links_in_external_app
+ pref_key_studies
+ pref_key_secret_settings
+ pref_key_use_nimbus_preview
+
+ has_opened_new_tab
+ has_added_to_home_screen
+ has_requested_desktop
+ app_launch_count
+ safe_browsing_category
+
+ security_category
+
+ pref_key_search_widget_installed
+ pref_key_clear_browsing_sessions
+
+
+ pref_screen_exceptions
+
+ pref_cfr_visibility_for_tracking_protection
+ pref_cfr_visibility_for_start_browsing
+
+ pref_tool_tip_privacy_security_settings
+
+ pref_key_light_theme
+ pref_key_dark_theme
+ pref_key_default_theme
+
+
+ pref_key_allow_autoplay_audio_video
+ pref_key_block_autoplay_audio_only
+ pref_key_block_autoplay_audio_video
+ pref_key_ask_to_allow
+ pref_key_blocked
+ pref_key_allowed
+
+ pref_key_privacy_site_permissions
+ pref_key_autoplay
+ pref_key_browser_feature_media_key_system_access
+ pref_key_phone_feature_camera
+ pref_key_phone_feature_location
+ pref_key_phone_feature_microphone
+ pref_key_phone_feature_notification
+
+
+ pref_key_onboarding_step
+ pref_key_first_screen
+ pref_key_second_screen
+ firstrun_shown
+ new_onboarding_enabled
+
+
+ pref_key_cookie_banner_enabled
+ pref_key_cookie_banner_settings
+ pref_key_cookie_banner_reject_all
+ pref_key_cookie_banner_disabled
+ pref_cfr_visibility_for_cookie_banner
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/static_strings.xml b/mobile/android/focus-android/app/src/main/res/values/static_strings.xml
new file mode 100644
index 0000000000..c5deff31e5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/static_strings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Secret Settings
+
+
+ Use Nimbus Preview Collection (requires restart)
+
+
+ Debug menu: %1$d click(s) left to enable
+ Debug menu enabled
+
+
+ LeakCanary
+
+
+ AS
+
+
+ Use Remote Settings Production server \n(Staging will be used when disabled) \n(requires restart)
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/strings.xml b/mobile/android/focus-android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..cec6aa586a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/strings.xml
@@ -0,0 +1,1069 @@
+
+
+
+
+
+
+
+
+ Cancel
+
+ OK
+
+ Save
+
+
+ Search or enter address
+
+ Automatic private browsing.\nBrowse. Erase. Repeat.
+
+
+ Your browsing history has been erased.
+ Browsing history cleared
+
+
+ Tab’s browsing history has been erased.
+
+
+ Search for %1$s
+
+
+ Share…
+
+
+ Report Site Issue
+
+
+ Open in %1$s
+
+
+ Open in…
+
+
+ Add to Home screen
+
+
+ Add to Shortcuts
+
+ Remove from Shortcuts
+
+
+ Settings
+ About
+ Help
+ Your Rights
+
+
+ Trackers blocked
+
+
+ Turning this off may fix some site problems
+
+
+ Content Blocking
+
+ Turn off to fix some sites
+
+
+ Powered by %1$s
+
+
+ Share via
+
+ Erase browsing history?
+ Tap or clear this notification to securely erase your browsing history.
+
+
+ Tap or swipe this notification to securely erase your browsing history.
+
+ Erase browsing history
+
+
+ Open
+
+
+ Erase and Open
+
+
+ Erase
+
+
+ Erase browsing history
+
+
+
+ Erase & open
+
+
+ Erase and open %1$s
+
+
+ Search in Focus
+
+ Search in Klar
+
+ Search in Focus Beta
+
+ Search in Focus Nightly
+
+
+ %1$s puts you in control.
+Use it as a private browser:
+
+ Search and browse right in the app
+ Block trackers (or update settings to allow trackers)
+ Erase to delete cookies as well as search and browsing history
+
+
+%1$s is produced by Mozilla. Our mission is to foster a healthy, open Internet.
+Learn more
]]>
+
+
+ Privacy & Security
+
+
+ Tracking, cookies, data choices
+
+
+ Set default, autocomplete
+
+
+
+
+ About %1$s, help
+
+
+ Enhanced Tracking Protection
+
+
+ Web Content
+
+
+ Switching Apps
+
+
+ General
+
+
+ Default browser, language
+
+
+ Data Collection & Use
+
+ Search
+
+
+ Get search suggestions
+
+ %1$s will send what you type in the address bar to your search engine
+
+
+ Default
+
+
+ Search engine
+
+
+ On
+
+
+ Off
+
+
+ URL Autocomplete
+
+
+ For top sites
+
+
+ Enable to have %s autocomplete over 450 popular URLs in the address bar.
+
+
+ For sites you add
+
+
+ Enable to have %s autocomplete your favorite URLs.
+
+
+ Manage sites
+
+
+ Manage sites
+
+
+ + Add custom URL
+
+
+ Your autocomplete list:
+
+
+ Add URL
+
+
+ Add custom URL
+
+
+ Add custom URL
+
+
+ Add link to autocomplete
+
+
+ Cookies and Site Data
+
+
+ Data Choices
+
+
+ Remove custom URLs
+
+
+ Learn more
+
+
+ Add and manage custom autocomplete URLs.
+
+
+ URL to add
+
+
+ Paste or enter URL
+
+
+ Example: mozilla.org
+
+
+ Example: example.com
+
+
+ New custom URL added.
+
+
+ Remove
+
+
+ Remove
+
+
+ Double-check the URL you entered.
+
+ Language
+
+ System default
+
+ Privacy
+ Block ad trackers
+ Some ads track site visits, even if you don’t click the ads
+ Block analytic trackers
+ Used to collect, analyze and measure activities like tapping and scrolling
+ Block social trackers
+ Embedded on sites to track your visits and to display functionality like share buttons
+ Block other content trackers
+ Enabling may cause some pages to behave unexpectedly
+ Block cookies
+
+
+ No thanks
+ Block 3rd-party tracker cookies only
+ Block 3rd-party cookies only
+ Block cross-site cookies
+ Yes please
+
+
+ Use fingerprint to unlock app
+
+ Unlock using fingerprint if you’ve added Shortcuts or when a website is already open in %s.
+
+
+ Stealth
+
+ Hide webpages when switching apps and block taking screenshots.
+
+ Security
+
+ Performance
+ Block web fonts
+
+ May result in missing icons or images
+
+ Block JavaScript
+
+ Pages may load faster, but may also behave unexpectedly
+
+
+ Make %1$s default browser
+
+ Mozilla
+ Send usage data
+
+
+ Learn more
+
+
+ Mozilla strives to collect only what we need to provide and improve %1$s for everyone.
+
+
+ Privacy Notice
+
+
+ Licensing information
+
+
+ Libraries that we use
+
+
+ %s | OSS Libraries
+
+
+ About %1$s
+
+
+ Installed search engines
+
+
+ Choose search engine
+
+
+ Restore default search engines
+
+
+ + Add another search engine
+ Remove search engines
+ Remove
+
+ Add another search engine
+
+ Select your preferred engine:
+
+
+ Add search engine
+
+ Search engine name
+ Search string to use
+ Save
+
+
+ Example: example.com/search/?q=%s
+
+ New search engine added.
+
+ Enter search engine name
+ An installed search engine is already using that name.
+
+ Enter search string
+
+ Check that search string matches Example format
+
+
+ Clear input
+
+
+ Dismiss
+
+
+ Erase browsing history
+
+
+ Tabs open: %1$s
+
+
+ Secure connection
+
+
+ Loading
+
+
+ Website loaded
+
+
+ More options
+
+
+ More options button
+
+
+ Navigate forward
+
+
+ Reload website
+
+
+ Navigate back
+
+
+ Stop loading website
+
+
+ Return to previous app
+
+
+ Number of trackers blocked
+
+
+ Block trackers
+
+ Your Rights
+
+ Open link in another app
+
+ You can leave %1$s to open this link in %2$s.
+
+ Find an app that can open link
+
+ None of the apps on your device are able to open this link. You can leave %1$s to search %2$s for an app that can.
+
+ Exit Private Browsing?
+
+
+ %1$s finished
+
+
+ Open
+
+
+ Added to shortcuts!
+
+ Server not found
+
+
+ Close
+
+
+
+ Welcome to %1$s
+
+
+ Fast. Private. No distractions.
+
+
+ Get started
+
+
+
+ %1$s isn’t like other browsers
+
+
+ We clear your history when you close the app for extra privacy.
+
+
+
+ Make %1$s your default to protect your data with every link you open.
+
+
+ Set as default browser
+
+
+ Skip
+
+
+ Power up your privacy
+
+ Take private browsing to the next level. Block ads and other content that can track you across sites and bog down page load times.
+
+
+ Your search, your way
+
+ Searching for something different? Choose another default search engine in Settings.
+
+
+ Add shortcuts to your home screen
+
+ Return to your favorite sites in %1$s quickly. Just select \"Add to Home screen\" from the %1$s menu.
+
+
+ Make privacy a habit
+
+ Set %1$s as your default browser and get the benefits of private browsing when you open webpages from other apps.
+
+ OK, got it!
+ Skip
+ Next
+
+
+ -
+
+
+ Add
+
+ YES
+
+
+ Cancel
+
+ NO
+
+
+ Shortcut will open with Enhanced Tracking Protection disabled
+
+
+ Private browsing session
+
+
+ Notifications let you erase your %1$s session with a tap. You don’t need to open the app or see what’s running in your browser.
+
+
+ Erase browsing history
+
+
+ Download Firefox
+
+
+
+
+
+ Mozilla Public License and other open source licenses.]]>
+
+
+ here.]]>
+
+
+ licenses.]]>
+
+
+ GNU General Public License v3, and available here .]]>
+
+
+ Username
+ Password
+ Clear
+
+
+
+ Secure Connection
+ Insecure Connection
+
+ Verified by: %1$s
+
+
+ Site Security
+ URL already exists
+
+
+ Find in Page
+
+
+ Find in page
+
+
+ %1$d/%2$d
+
+ %1$d out of %2$d
+
+
+ Find next result
+
+ Find previous result
+
+ Dismiss find in page
+
+
+ Request desktop site
+
+
+ Desktop site
+
+
+ URL copied
+
+
+ Developer tools
+
+
+ Open links in apps
+
+
+ Advanced
+
+
+ Site permissions
+
+
+ Cookie Banner Reduction
+
+
+ On
+
+
+ Off
+
+
+ Cookie Banner Reduction
+
+
+ See fewer banners by automatically rejecting cookie requests, when possible.
+
+ -->
+ Cookie Banner Reduction
+
+
+ ON for this site
+
+
+ Site currently not supported
+
+
+ OFF for this site
+
+
+ Cookie Banner Reduction
+
+
+ OFF for this site
+
+
+ ON for this site
+
+
+ Turn on Cookie Banner Reduction for %1$s?
+
+
+ Turn off Cookie Banner Reduction for %1$s?
+
+
+ %1$s will clear this site’s cookies and refresh the page. Clearing all cookies may sign you out or empty shopping carts.
+
+
+ %1$s can try to automatically reject cookie requests.
+
+
+ This site is currently not supported by Cookie Banner Reduction. Would you like to request our team review this website and add support in the future?
+
+
+ Cancel
+
+
+ Request support
+
+
+ Request to support site submitted.
+
+
+ Request to support site submitted.
+
+
+
+ %1$s tries to reject cookie requests to dismiss annoying cookie banners.\n\nManage cookie banner preferences in %2$s.
+
+ settings
+
+
+ Autoplay
+
+
+ To allow it:
+
+
+ 1. Go to Android Settings
+
+
+ Permissions]]>
+
+
+ Go to Settings
+
+
+ %1$s to ON]]>
+
+
+ Camera
+
+
+ Microphone
+
+
+ Location
+
+
+ Notification
+
+
+ DRM-controlled content
+
+
+ Ask to allow
+
+
+ Blocked
+
+
+ Allowed
+
+
+ Blocked by Android
+
+
+ Allow audio and video
+
+
+ Block audio only
+
+
+ Recommended
+
+
+ Block audio and video
+
+
+ Studies
+
+
+ Firefox may install and run studies from time to time.
+
+
+ Learn more
+
+
+ The application will quit to apply changes
+
+
+ Remove
+
+
+ Active
+
+
+ Completed
+
+
+ Remote debugging via USB/Wi-Fi
+
+
+ Unlock
+
+
+ Confirm Using Your Fingerprint
+
+
+ You can use your fingerprint to continue your current app session.
+
+
+ Open Link in New Session
+
+
+ Fingerprint icon
+
+
+ Fingerprint not recognized. Try again.
+
+
+ Finger moved too fast. Try again.
+
+
+ Show search suggestions?
+
+
+ To get suggestions, %1$s needs to send what you type in the address bar to the search engine.
+
+
+ No
+
+
+ Yes
+
+
+ Some search engines cannot show suggestions.
+
+
+ Dismiss
+
+
+
+
+ Site behaving unexpectedly?\n
+ Try turning off Tracking Protection
+
+
+ Add to Home screen]]>
+
+
+ Open every link in %1$s\n
+ Set %1$s as default browser
+
+
+
+ Autocomplete URLs for sites you use most\n
+ Long-press any URL in the address bar
+
+
+
+ Open a link in a new tab\n
+ Long-press any link on a page
+
+
+
+ Turn off tips on the start screen
+
+
+ New tab opened
+
+
+ Switch
+
+
+ Entering full screen mode
+
+
+ Switch to link in new tab immediately
+
+
+ Block potentially dangerous and deceptive sites
+
+ Block reported deceptive and attack sites, malware sites, and unwanted software sites.
+
+ HTTPS-Only Mode
+
+ Automatically attempts to connect to sites using the HTTPS encryption protocol for increased security.
+
+
+ Exceptions
+
+ You have disabled content blocking for these websites.
+
+ Remove
+
+ Remove all websites
+
+
+ Block Cookies
+
+
+ Would you like to block cookies?
+
+
+ Tab Crashed
+
+ Sorry. We’re having a problem with this tab.
+
+ As a private browser, we never save and cannot restore this tab.
+
+ Close Tab
+
+
+ Send crash report to Mozilla
+
+
+
+
+ Trackers blocked since %s
+
+ Content
+
+ Advertising
+
+ Social
+
+ Analytics
+
+ Enhanced Tracking Protection
+
+ Protections are OFF for this site
+
+ Protections are ON for this site
+
+ Connection is secure
+
+ Connection is not secure
+
+ Trackers and Scripts to Block
+
+
+ Go back
+
+
+
+ Remove
+
+ Rename
+
+ Rename
+
+ Shortcut name
+
+
+ Saved and shared images <b>will not be</b> deleted when you erase %1$s history
+
+
+
+ Theme
+
+ Light
+
+ Dark
+
+ Set by Battery Saver
+
+ Follow device theme
+
+
+ This site doesn’t support HTTPS
+
+
+ Learn more
+ Change this setting in Settings > Privacy & Security > Security.]]>
+
+
+ Connection not secure
+
+
+
+ If you’ve connected to this server successfully in the past, the error may be temporary.
+ ]]>
+
+
+ Someone could be trying to impersonate the site and continuing could be risky.
+
+ %1$s does not trust %2$s because its certificate issuer is unknown, the certificate is self-signed, or the server is not sending the correct intermediate certificates.
+ ]]>
+
+
+
+ Close tab
+
+
+
+ Got ‘em! We stopped this site from spying on you. Tap the shield any time to see what we’re blocking.
+
+
+ Close popup
+
+
+
+ You’re protected!
+
+ These default settings offer strong protection. But it’s easy to tweak the settings to meet your specific needs.
+
+ Dismiss
+
+
+ Tap here to trash it all — history, cookies, everything — and start fresh on a new tab.
+
+
+
+
+ Close
+
+
+ Search widget
+
+
+ Browsing history cleared! 🎉
+
+
+ Start your private browsing session, and we’ll block trackers and other bad stuff as you go.
+
+
+ We’ll leave you to your private browsing, but get a quicker start next time with the %1$s widget on your Home screen.
+
+
+ Add widget to home screen
+
+
+ Widget added to home screen
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/strings_references.xml b/mobile/android/focus-android/app/src/main/res/values/strings_references.xml
new file mode 100644
index 0000000000..e3301986fb
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/strings_references.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ yes
+ third_party_only
+ third_party_tracker
+ cross_site
+ no
+
+
+
+ - @string/yes
+ - @string/third_party_only
+ - @string/third_party_tracker
+
+ - @string/cross_site
+ - @string/no
+
+
+
+
+ - @string/preference_privacy_should_block_cookies_yes_option2
+ - @string/preference_privacy_should_block_cookies_third_party_only_option
+ - @string/preference_privacy_should_block_cookies_third_party_tracker_cookies_option
+ - @string/preference_privacy_should_block_cookies_cross_site_option
+ - @string/preference_privacy_should_block_cookies_no_option2
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/values/styles.xml b/mobile/android/focus-android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..33b0d1603d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/values/styles.xml
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/advanced_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/advanced_settings.xml
new file mode 100644
index 0000000000..0f16fceede
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/advanced_settings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/autocomplete.xml b/mobile/android/focus-android/app/src/main/res/xml/autocomplete.xml
new file mode 100644
index 0000000000..fa61ceea17
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/autocomplete.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/xml/cookie_banner_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/cookie_banner_settings.xml
new file mode 100644
index 0000000000..72a86488b1
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/cookie_banner_settings.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/experiments_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/experiments_settings.xml
new file mode 100644
index 0000000000..7df8fa43ec
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/experiments_settings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/xml/general_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/general_settings.xml
new file mode 100644
index 0000000000..31f5a2c136
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/general_settings.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/manual_add_search_engine.xml b/mobile/android/focus-android/app/src/main/res/xml/manual_add_search_engine.xml
new file mode 100644
index 0000000000..0a02fc726c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/manual_add_search_engine.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/xml/mozilla_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/mozilla_settings.xml
new file mode 100644
index 0000000000..580f0ee3a4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/mozilla_settings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/privacy_security_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/privacy_security_settings.xml
new file mode 100644
index 0000000000..0163e41e49
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/privacy_security_settings.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/provider_paths.xml b/mobile/android/focus-android/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000000..dd8645428d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/remove_search_engines.xml b/mobile/android/focus-android/app/src/main/res/xml/remove_search_engines.xml
new file mode 100644
index 0000000000..edbfaa4371
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/remove_search_engines.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/main/res/xml/search_engine_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/search_engine_settings.xml
new file mode 100644
index 0000000000..07d086073b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/search_engine_settings.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/search_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/search_settings.xml
new file mode 100644
index 0000000000..effbc273d6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/search_settings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/search_widget_info.xml b/mobile/android/focus-android/app/src/main/res/xml/search_widget_info.xml
new file mode 100644
index 0000000000..e2d3825407
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/search_widget_info.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/secret_settings.xml b/mobile/android/focus-android/app/src/main/res/xml/secret_settings.xml
new file mode 100644
index 0000000000..2d61913bce
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/secret_settings.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/settings.xml b/mobile/android/focus-android/app/src/main/res/xml/settings.xml
new file mode 100644
index 0000000000..2cc5cd9d7e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/settings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/main/res/xml/site_permissions.xml b/mobile/android/focus-android/app/src/main/res/xml/site_permissions.xml
new file mode 100644
index 0000000000..e6df085614
--- /dev/null
+++ b/mobile/android/focus-android/app/src/main/res/xml/site_permissions.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/ic_launcher-playstore.png b/mobile/android/focus-android/app/src/nightly/ic_launcher-playstore.png
new file mode 100644
index 0000000000..7093bcc33a
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/ic_launcher-playstore.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/utils/AdjustHelper.java b/mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/utils/AdjustHelper.java
new file mode 100644
index 0000000000..b0aa2e1835
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/utils/AdjustHelper.java
@@ -0,0 +1,14 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.focus.utils;
+
+import android.content.Context;
+
+public class AdjustHelper {
+ public static void setupAdjustIfNeeded(Context context) {
+ // DEBUG: No Adjust - This class has different implementations for all build types.
+ }
+}
diff --git a/mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/web/Config.kt b/mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/web/Config.kt
new file mode 100644
index 0000000000..415d487a8e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/java/org/mozilla/focus/web/Config.kt
@@ -0,0 +1,10 @@
+/* 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.focus.web
+
+object Config {
+ const val EXPERIMENT_DESCRIPTOR_GECKOVIEW_ENGINE = "use-gecko-nightly"
+ const val EXPERIMENT_DESCRIPTOR_HOME_SCREEN_TIPS = "use-homescreen-tips-nightly"
+}
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable-land/dark_background.xml b/mobile/android/focus-android/app/src/nightly/res/drawable-land/dark_background.xml
new file mode 100644
index 0000000000..a9f901ac74
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable-land/dark_background.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable-v24/ic_splash_screen.xml b/mobile/android/focus-android/app/src/nightly/res/drawable-v24/ic_splash_screen.xml
new file mode 100644
index 0000000000..4be0323b4c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable-v24/ic_splash_screen.xml
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable/dark_background.xml b/mobile/android/focus-android/app/src/nightly/res/drawable/dark_background.xml
new file mode 100644
index 0000000000..90f50cf3ff
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable/dark_background.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_background.xml b/mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..d2c843b554
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_foreground.xml b/mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..4be0323b4c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable/ic_splash_screen.png b/mobile/android/focus-android/app/src/nightly/res/drawable/ic_splash_screen.png
new file mode 100644
index 0000000000..b8be2b7181
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/drawable/ic_splash_screen.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable/onboarding_logo.xml b/mobile/android/focus-android/app/src/nightly/res/drawable/onboarding_logo.xml
new file mode 100644
index 0000000000..5c1ddc7761
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable/onboarding_logo.xml
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/drawable/wordmark2.xml b/mobile/android/focus-android/app/src/nightly/res/drawable/wordmark2.xml
new file mode 100644
index 0000000000..ce348bd3ae
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/drawable/wordmark2.xml
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:fillAlpha="0.8"/>
+
+ android:fillAlpha="0.8"/>
+
+ android:fillAlpha="0.8"/>
+
+ android:fillAlpha="0.8"/>
+
+ android:fillAlpha="0.8"/>
+
+ android:fillAlpha="0.8"/>
+
+
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/android/focus-android/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..1b3296f41d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..57266ce663
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..200d210606
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..6035524f91
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..92290c2ddd
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..e62f9490c2
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..c8a9087966
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..efbf73d970
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..a241232589
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..40f1e0d060
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..00c87c538d
Binary files /dev/null and b/mobile/android/focus-android/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/mobile/android/focus-android/app/src/nightly/res/values-night/colors.xml b/mobile/android/focus-android/app/src/nightly/res/values-night/colors.xml
new file mode 100644
index 0000000000..38c1ed8f3e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/nightly/res/values-night/colors.xml
@@ -0,0 +1,7 @@
+
+
+
+ #f0f0f4
+
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/BrowserFragmentTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/BrowserFragmentTest.kt
new file mode 100644
index 0000000000..dd89505a94
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/BrowserFragmentTest.kt
@@ -0,0 +1,108 @@
+/* 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.focus
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.concept.engine.selection.SelectionActionDelegate
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertNotNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mozilla.focus.databinding.FragmentBrowserBinding
+import org.mozilla.focus.widget.ResizableKeyboardCoordinatorLayout
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class BrowserFragmentTest {
+ @Test
+ fun testEngineViewInflationAndParentInteraction() {
+ val layoutInflater = LayoutInflater.from(testContext)
+
+ // Intercept the inflation process
+ layoutInflater.factory2 = object : LayoutInflater.Factory2 {
+ override fun onCreateView(
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet,
+ ): View? {
+ // Inflate a DummyEngineView when trying to inflate an EngineView
+ if (name == EngineView::class.java.name) {
+ return DummyEngineView(testContext)
+ }
+
+ // For other types of views, let the system handle it
+ return null
+ }
+
+ override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
+ return onCreateView(null, name, context, attrs)
+ }
+ }
+
+ val binding = FragmentBrowserBinding.inflate(LayoutInflater.from(testContext))
+ val engineView: EngineView = binding.engineView
+
+ assertNotNull(engineView)
+
+ // Get the layout parent of the EngineView
+ val engineViewParent = spy(
+ (engineView as View).parent as ResizableKeyboardCoordinatorLayout,
+ )
+
+ assertNotNull(engineViewParent)
+
+ engineViewParent.requestDisallowInterceptTouchEvent(true)
+
+ // Verify that the EngineView's parent does not propagate requestDisallowInterceptTouchEvent
+ verify(engineViewParent).requestDisallowInterceptTouchEvent(true)
+ // If propagated, an additional ViewGroup.requestDisallowInterceptTouchEvent would have been registered.
+ verifyNoMoreInteractions(engineViewParent)
+ }
+}
+
+/**
+ * Dummy implementation of the EngineView interface.
+ */
+class DummyEngineView(context: Context) : View(context), EngineView {
+ init {
+ id = R.id.engineView
+ }
+
+ override fun render(session: EngineSession) {
+ // no-op
+ }
+
+ override fun release() {
+ // no-op
+ }
+
+ override fun captureThumbnail(onFinish: (Bitmap?) -> Unit) {
+ // no-op
+ }
+
+ override fun setVerticalClipping(clippingHeight: Int) {
+ // no-op
+ }
+
+ override fun setDynamicToolbarMaxHeight(height: Int) {
+ // no-op
+ }
+
+ override fun setActivityContext(context: Context?) {
+ // no-op
+ }
+
+ override var selectionActionDelegate: SelectionActionDelegate? = null
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt
new file mode 100644
index 0000000000..7892c347b6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/TestFocusApplication.kt
@@ -0,0 +1,95 @@
+/* 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.focus
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.JsonReader
+import android.util.JsonWriter
+import mozilla.components.browser.engine.gecko.profiler.Profiler
+import mozilla.components.concept.engine.DefaultSettings
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.EngineSessionState
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.concept.engine.Settings
+import mozilla.components.concept.engine.utils.EngineVersion
+import mozilla.components.concept.engine.webextension.WebExtensionDelegate
+import mozilla.components.concept.fetch.Client
+import mozilla.components.concept.fetch.Request
+import mozilla.components.concept.fetch.Response
+import org.json.JSONObject
+
+/**
+ * [FocusApplication] override for unit tests. This allows us to override some parameters and inputs
+ * since an application object gets created without much control otherwise.
+ */
+class TestFocusApplication : FocusApplication() {
+ override val components: Components by lazy {
+ Components(this, engineOverride = FakeEngine(), clientOverride = FakeClient())
+ }
+
+ override fun initializeNimbus() = Unit
+}
+
+/**
+ * Empty [FocusApplication] override for unit tests.
+ */
+class EmptyFocusApplication : FocusApplication() {
+ override fun onCreate() {
+ //
+ }
+}
+
+// Borrowed this from AC unit tests. This is something we should consider moving to support-test, so
+// that everyone who needs an Engine in unit tests can use it. It also allows us to enhance this mock
+// and maybe do some actual things that help in tests. :)
+class FakeEngine : Engine {
+ override val version: EngineVersion
+ get() = throw NotImplementedError("Not needed for test")
+
+ override fun createView(context: Context, attrs: AttributeSet?): EngineView =
+ throw UnsupportedOperationException()
+
+ override fun createSession(private: Boolean, contextId: String?): EngineSession =
+ throw UnsupportedOperationException()
+
+ override fun createSessionState(json: JSONObject) = FakeEngineSessionState()
+
+ override fun createSessionStateFrom(reader: JsonReader): EngineSessionState {
+ reader.beginObject()
+ reader.endObject()
+ return FakeEngineSessionState()
+ }
+
+ override fun name(): String =
+ throw UnsupportedOperationException()
+
+ override fun speculativeConnect(url: String) =
+ throw UnsupportedOperationException()
+
+ override val profiler: Profiler
+ get() = throw NotImplementedError("Not needed for test")
+
+ override val settings: Settings = DefaultSettings()
+
+ override fun registerWebExtensionDelegate(webExtensionDelegate: WebExtensionDelegate) {
+ // Intentionally empty to avoid "UnsupportedOperationException: Web extension support
+ // is not available in this engine" error in unit tests
+ }
+}
+
+class FakeEngineSessionState : EngineSessionState {
+ override fun writeTo(writer: JsonWriter) {
+ writer.beginObject()
+ writer.endObject()
+ }
+}
+
+class FakeClient : Client() {
+ override fun fetch(request: Request): Response {
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/animation/TransitionDrawableGroupTest.java b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/animation/TransitionDrawableGroupTest.java
new file mode 100644
index 0000000000..d21307eb4b
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/animation/TransitionDrawableGroupTest.java
@@ -0,0 +1,42 @@
+/* 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.focus.animation;
+
+import android.graphics.drawable.TransitionDrawable;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class TransitionDrawableGroupTest {
+ @Test
+ public void testStartIsCalledOnAllItems() {
+ final TransitionDrawable transitionDrawable1 = mock(TransitionDrawable.class);
+ final TransitionDrawable transitionDrawable2 = mock(TransitionDrawable.class);
+
+ final TransitionDrawableGroup group = new TransitionDrawableGroup(
+ transitionDrawable1, transitionDrawable2);
+
+ group.startTransition(2500);
+
+ verify(transitionDrawable1).startTransition(2500);
+ verify(transitionDrawable2).startTransition(2500);
+ }
+
+ @Test
+ public void testResetIsCalledOnAllItems() {
+ final TransitionDrawable transitionDrawable1 = mock(TransitionDrawable.class);
+ final TransitionDrawable transitionDrawable2 = mock(TransitionDrawable.class);
+
+ final TransitionDrawableGroup group = new TransitionDrawableGroup(
+ transitionDrawable1, transitionDrawable2);
+
+ group.resetTransition();
+
+ verify(transitionDrawable1).resetTransition();
+ verify(transitionDrawable2).resetTransition();
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentTest.kt
new file mode 100644
index 0000000000..7c19fcdb91
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentTest.kt
@@ -0,0 +1,70 @@
+/* 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.focus.biometrics
+
+import android.content.Context
+import android.os.Build
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import mozilla.components.lib.auth.AuthenticationDelegate
+import mozilla.components.lib.auth.BiometricPromptAuth
+import mozilla.components.support.test.mock
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.robolectric.annotation.Config
+
+class BiometricAuthenticationFragmentTest {
+ private lateinit var biometricPromptAuth: BiometricPromptAuth
+ private lateinit var fragment: BiometricAuthenticationFragment
+ private val activity: FragmentActivity = mock()
+ private val testContext: Context = mock()
+
+ private val fragmentManger: FragmentManager = mock()
+ private val fragmentTransaction: FragmentTransaction = mock()
+
+ @Before
+ fun setup() {
+ fragment = spy(BiometricAuthenticationFragment())
+ doReturn(testContext).`when`(fragment).context
+ doReturn(activity).`when`(fragment).requireActivity()
+ doReturn(fragmentManger).`when`(activity).supportFragmentManager
+ doReturn(fragmentTransaction).`when`(fragmentManger).beginTransaction()
+ biometricPromptAuth = spy(
+ BiometricPromptAuth(
+ testContext,
+ fragment,
+ object : AuthenticationDelegate {
+ override fun onAuthError(errorText: String) {
+ }
+ override fun onAuthFailure() {
+ }
+ override fun onAuthSuccess() {
+ }
+ },
+ ),
+ )
+ }
+
+ @Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
+ @Test
+ fun `GIVEN biometric authentication fragment WHEN show biometric prompt is called and can use feature returns false THEN request authentication is not called`() {
+ fragment.showBiometricPrompt(biometricPromptAuth, "title", "subtitle")
+
+ verify(biometricPromptAuth, never()).requestAuthentication("title", "subtitle")
+ }
+
+ @Test
+ fun `GIVEN biometric authentication fragment WHEN on Auth Error is called THEN biometricErrorText should be updated`() {
+ fragment.onAuthError("Fingerprint operation canceled by user.")
+
+ assertEquals(fragment.biometricErrorText.value, "Fingerprint operation canceled by user.")
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegrationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegrationTest.kt
new file mode 100644
index 0000000000..179fafeb2c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegrationTest.kt
@@ -0,0 +1,234 @@
+/* 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.focus.browser.integration
+
+import android.view.View
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.state.SecurityInfoState
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.browser.state.state.createTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.browser.toolbar.display.DisplayToolbar.Indicators
+import mozilla.components.support.test.ext.joinBlocking
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.test.whenever
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mozilla.focus.fragment.BrowserFragment
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class BrowserToolbarIntegrationTest {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val selectedTab = createSecureTab()
+
+ private lateinit var toolbar: BrowserToolbar
+
+ @Mock
+ private lateinit var fragment: BrowserFragment
+
+ private lateinit var browserToolbarIntegration: BrowserToolbarIntegration
+
+ @Mock
+ private lateinit var fragmentView: View
+
+ private lateinit var store: BrowserStore
+
+ @Before
+ @ExperimentalCoroutinesApi
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+ Dispatchers.setMain(testDispatcher)
+ store = spy(
+ BrowserStore(
+ initialState = BrowserState(
+ tabs = listOf(selectedTab),
+ selectedTabId = selectedTab.id,
+ ),
+ ),
+ )
+
+ toolbar = BrowserToolbar(testContext)
+
+ whenever(fragment.resources).thenReturn(testContext.resources)
+ whenever(fragment.context).thenReturn(testContext)
+ whenever(fragment.view).thenReturn(fragmentView)
+ whenever(fragment.requireContext()).thenReturn(testContext)
+
+ browserToolbarIntegration = spy(
+ BrowserToolbarIntegration(
+ store = store,
+ toolbar = toolbar,
+ fragment = fragment,
+ controller = mock(),
+ sessionUseCases = mock(),
+ customTabsUseCases = mock(),
+ onUrlLongClicked = { false },
+ eraseActionListener = {},
+ tabCounterListener = {},
+ inTesting = true,
+ ),
+ )
+ }
+
+ @After
+ @ExperimentalCoroutinesApi
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `WHEN starting THEN observe security changes`() {
+ doNothing().`when`(browserToolbarIntegration).observerSecurityIndicatorChanges()
+
+ browserToolbarIntegration.start()
+
+ verify(browserToolbarIntegration).observerSecurityIndicatorChanges()
+ }
+
+ @Test
+ fun `WHEN start method is called THEN observe erase tabs CFR changes`() {
+ doNothing().`when`(browserToolbarIntegration).observeEraseCfr()
+
+ browserToolbarIntegration.start()
+
+ verify(browserToolbarIntegration).observeEraseCfr()
+ }
+
+ @Test
+ fun `WHEN start method is called THEN observe tracking protection CFR changes`() {
+ doNothing().`when`(browserToolbarIntegration).observeTrackingProtectionCfr()
+
+ browserToolbarIntegration.start()
+
+ verify(browserToolbarIntegration).observeTrackingProtectionCfr()
+ }
+
+ @Test
+ fun `WHEN stopping THEN stop tracking protection CFR changes`() {
+ doNothing().`when`(browserToolbarIntegration).stopObserverTrackingProtectionCfrChanges()
+
+ browserToolbarIntegration.stop()
+
+ verify(browserToolbarIntegration).stopObserverTrackingProtectionCfrChanges()
+ }
+
+ @Test
+ fun `WHEN stopping THEN stop erase tabs CFR changes`() {
+ doNothing().`when`(browserToolbarIntegration).stopObserverEraseTabsCfrChanges()
+
+ browserToolbarIntegration.stop()
+
+ verify(browserToolbarIntegration).stopObserverEraseTabsCfrChanges()
+ }
+
+ @Test
+ fun `WHEN stopping THEN stop observe security changes`() {
+ doNothing().`when`(browserToolbarIntegration).stopObserverSecurityIndicatorChanges()
+
+ browserToolbarIntegration.stop()
+
+ verify(browserToolbarIntegration).stopObserverSecurityIndicatorChanges()
+ }
+
+ @Test
+ fun `GIVEN an insecure site WHEN observing security changes THEN add the security icon`() {
+ browserToolbarIntegration.start()
+
+ updateSecurityStatus(secure = false)
+
+ verify(browserToolbarIntegration).addSecurityIndicator()
+ assertEquals(listOf(Indicators.SECURITY), toolbar.display.indicators)
+ }
+
+ @Test
+ fun `GIVEN an about site WHEN observing security changes THEN DO NOT add the security icon`() {
+ browserToolbarIntegration.start()
+
+ updateTabUrl("about:")
+
+ verify(browserToolbarIntegration, times(0)).addSecurityIndicator()
+ assertEquals(listOf(Indicators.TRACKING_PROTECTION), toolbar.display.indicators)
+ }
+
+ @Test
+ fun `GIVEN a secure site after a previous insecure site WHEN observing security changes THEN add the tracking protection icon`() {
+ browserToolbarIntegration.start()
+
+ updateSecurityStatus(secure = false)
+
+ verify(browserToolbarIntegration).addSecurityIndicator()
+
+ updateSecurityStatus(secure = true)
+
+ verify(browserToolbarIntegration).addTrackingProtectionIndicator()
+ assertEquals(listOf(Indicators.TRACKING_PROTECTION), toolbar.display.indicators)
+ }
+
+ @Test
+ fun `WHEN the integration starts THEN start the toolbarController`() {
+ browserToolbarIntegration.toolbarController = mock()
+
+ browserToolbarIntegration.start()
+
+ verify(browserToolbarIntegration.toolbarController).start()
+ }
+
+ @Test
+ fun `WHEN the integration stops THEN stop the toolbarController`() {
+ browserToolbarIntegration.toolbarController = mock()
+
+ browserToolbarIntegration.stop()
+
+ verify(browserToolbarIntegration.toolbarController).stop()
+ }
+
+ private fun updateSecurityStatus(secure: Boolean) {
+ store.dispatch(
+ ContentAction.UpdateSecurityInfoAction(
+ selectedTab.id,
+ SecurityInfoState(
+ secure = secure,
+ host = "mozilla.org",
+ issuer = "Mozilla",
+ ),
+ ),
+ ).joinBlocking()
+
+ testDispatcher.scheduler.advanceUntilIdle()
+ }
+
+ private fun updateTabUrl(url: String) {
+ store.dispatch(
+ ContentAction.UpdateUrlAction(selectedTab.id, url),
+ ).joinBlocking()
+
+ testDispatcher.scheduler.advanceUntilIdle()
+ }
+
+ private fun createSecureTab(): TabSessionState {
+ val tab = createTab("https://www.mozilla.org", id = "1")
+ return tab.copy(
+ content = tab.content.copy(securityInfo = SecurityInfoState(secure = true)),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FindInPageIntegrationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FindInPageIntegrationTest.kt
new file mode 100644
index 0000000000..21222e9e8c
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FindInPageIntegrationTest.kt
@@ -0,0 +1,46 @@
+/* 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.focus.browser.integration
+
+import androidx.core.view.isVisible
+import mozilla.components.browser.state.state.ContentState
+import mozilla.components.browser.state.state.EngineState
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.feature.findinpage.view.FindInPageBar
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.whenever
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.spy
+
+internal class FindInPageIntegrationTest {
+ // For ease of tests naming "find in page bar" is referred to as FIPB.
+ val toolbar: BrowserToolbar = mock()
+ val findInPageBar: FindInPageBar = mock()
+
+ @Test
+ fun `GIVEN FIPB not shown WHEN show is called THEN FIPB is shown`() {
+ val feature = spy(FindInPageIntegration(mock(), findInPageBar, toolbar, mock()))
+ val sessionState: SessionState = mock()
+ val contentState: ContentState = mock()
+ val engineState: EngineState = mock()
+ whenever(sessionState.content).thenReturn(contentState)
+ whenever(sessionState.engineState).thenReturn(engineState)
+
+ feature.show(sessionState)
+
+ Mockito.verify(findInPageBar).isVisible = true
+ }
+
+ @Test
+ fun `GIVEN FIPB is shown WHEN hide is called THEN FIPB is hidden`() {
+ val feature = spy(FindInPageIntegration(mock(), findInPageBar, toolbar, mock()))
+
+ feature.hide()
+
+ Mockito.verify(findInPageBar).isVisible = false
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
new file mode 100644
index 0000000000..3f9d9270c4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
@@ -0,0 +1,374 @@
+/* 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.focus.browser.integration
+
+import android.app.Activity
+import android.content.res.Resources
+import android.view.View
+import android.view.Window
+import android.view.WindowManager
+import androidx.core.view.isVisible
+import mozilla.components.browser.engine.gecko.GeckoEngineView
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.feature.prompts.dialog.FullScreenNotification
+import mozilla.components.feature.session.FullScreenFeature
+import mozilla.components.support.test.any
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mozilla.focus.ext.disableDynamicBehavior
+import org.mozilla.focus.ext.enableDynamicBehavior
+import org.mozilla.focus.ext.hide
+import org.mozilla.focus.ext.showAsFixed
+import org.mozilla.focus.utils.Settings
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+internal class FullScreenIntegrationTest {
+ @Test
+ fun `WHEN the integration is started THEN start FullScreenFeature`() {
+ val feature: FullScreenFeature = mock()
+ val integration = FullScreenIntegration(
+ mock(),
+ mock(),
+ null,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ ).apply {
+ this.feature = feature
+ }
+
+ integration.start()
+
+ verify(feature).start()
+ }
+
+ @Test
+ fun `WHEN the integration is stopped THEN stop FullScreenFeature`() {
+ val feature: FullScreenFeature = mock()
+ val integration = FullScreenIntegration(
+ mock(),
+ mock(),
+ null,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ ).apply {
+ this.feature = feature
+ }
+
+ integration.stop()
+
+ verify(feature).stop()
+ }
+
+ @Test
+ fun `WHEN back is pressed THEN send this to the feature`() {
+ val feature: FullScreenFeature = mock()
+ val integration = FullScreenIntegration(
+ mock(),
+ mock(),
+ null,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ ).apply {
+ this.feature = feature
+ }
+
+ integration.onBackPressed()
+
+ verify(feature).onBackPressed()
+ }
+
+ @Test
+ fun `WHEN the viewport changes THEN update layoutInDisplayCutoutMode`() {
+ val windowAttributes = WindowManager.LayoutParams()
+ val activityWindow: Window = mock()
+ val activity: Activity = mock()
+ doReturn(activityWindow).`when`(activity).window
+ doReturn(windowAttributes).`when`(activityWindow).attributes
+ val integration = FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ )
+
+ integration.viewportFitChanged(33)
+
+ assertEquals(33, windowAttributes.layoutInDisplayCutoutMode)
+ }
+
+ @Test
+ @Suppress("DEPRECATION")
+ fun `WHEN entering immersive mode THEN hide all system bars`() {
+ val decorView: View = mock()
+ val activityWindow: Window = mock()
+ val activity: Activity = mock()
+ val layoutParams: WindowManager.LayoutParams = mock()
+ doReturn(activityWindow).`when`(activity).window
+ doReturn(decorView).`when`(activityWindow).decorView
+ doReturn(layoutParams).`when`(activityWindow).attributes
+
+ val integration = FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ )
+
+ integration.switchToImmersiveMode()
+
+ // verify entering immersive mode
+ verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
+ verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ // verify that the immersive mode restoration is set as expected
+ verify(decorView).setOnApplyWindowInsetsListener(any())
+ }
+
+ @Test
+ @Suppress("DEPRECATION")
+ fun `GIVEN immersive mode WHEN exitImmersiveModeIfNeeded is called THEN show the system bars`() {
+ val decorView: View = mock()
+ val activityWindow: Window = mock()
+ val activity: Activity = mock()
+ val layoutParams: WindowManager.LayoutParams = mock()
+ doReturn(activityWindow).`when`(activity).window
+ doReturn(decorView).`when`(activityWindow).decorView
+ doReturn(layoutParams).`when`(activityWindow).attributes
+ val integration = FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ )
+
+ integration.exitImmersiveMode()
+ // Hiding the system bar hides the status and navigation bars.
+ // setSystemUiVisibility will be called twice by WindowInsetsControllerCompat
+ // once for setting the status bar and another for setting the navigation bar
+ verify(decorView, times(2)).systemUiVisibility
+ verify(decorView).setOnApplyWindowInsetsListener(null)
+ }
+
+ @Test
+ fun `GIVEN a11y is enabled WHEN enterBrowserFullscreen THEN hide the toolbar`() {
+ val toolbar: BrowserToolbar = mock()
+ val engineView: GeckoEngineView = mock()
+ doReturn(mock()).`when`(engineView).asView()
+ val settings: Settings = mock()
+ doReturn(true).`when`(settings).isAccessibilityEnabled()
+ val integration = FullScreenIntegration(
+ mock(),
+ mock(),
+ null,
+ mock(),
+ settings,
+ toolbar,
+ mock(),
+ engineView,
+ mock(),
+ )
+
+ integration.enterBrowserFullscreen()
+
+ verify(toolbar).hide(engineView)
+ verify(toolbar, never()).collapse()
+ verify(toolbar, never()).disableDynamicBehavior(engineView)
+ }
+
+ @Test
+ fun `GIVEN a11y is disabled WHEN enterBrowserFullscreen THEN collapse and disable the dynamic toolbar`() {
+ val toolbar: BrowserToolbar = mock()
+ val engineView: GeckoEngineView = mock()
+ doReturn(mock()).`when`(engineView).asView()
+ val settings: Settings = mock()
+ doReturn(false).`when`(settings).isAccessibilityEnabled()
+ val integration = FullScreenIntegration(
+ mock(),
+ mock(),
+ null,
+ mock(),
+ settings,
+ toolbar,
+ mock(),
+ engineView,
+ mock(),
+ )
+
+ integration.enterBrowserFullscreen()
+
+ verify(toolbar, never()).hide(engineView)
+ with(inOrder(toolbar)) {
+ verify(toolbar).collapse()
+ verify(toolbar).disableDynamicBehavior(engineView)
+ }
+ }
+
+ @Test
+ fun `GIVEN a11y is enabled WHEN exitBrowserFullscreen THEN show the toolbar`() {
+ val toolbar: BrowserToolbar = mock()
+ val engineView: GeckoEngineView = mock()
+ doReturn(mock()).`when`(engineView).asView()
+ val settings: Settings = mock()
+ doReturn(true).`when`(settings).isAccessibilityEnabled()
+ val resources: Resources = mock()
+ val activity: Activity = mock()
+ doReturn(resources).`when`(activity).resources
+ val integration = FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ settings,
+ toolbar,
+ mock(),
+ engineView,
+ mock(),
+ )
+
+ integration.exitBrowserFullscreen()
+
+ verify(toolbar).showAsFixed(activity, engineView)
+ verify(toolbar, never()).expand()
+ verify(toolbar, never()).enableDynamicBehavior(activity, engineView)
+ }
+
+ @Test
+ fun `GIVEN a11y is disabled WHEN exitBrowserFullscreen THEN enable the dynamic toolbar and expand it`() {
+ val toolbar: BrowserToolbar = mock()
+ val engineView: GeckoEngineView = mock()
+ doReturn(mock()).`when`(engineView).asView()
+ val settings: Settings = mock()
+ doReturn(false).`when`(settings).isAccessibilityEnabled()
+ val resources: Resources = mock()
+ val activity: Activity = mock()
+ doReturn(resources).`when`(activity).resources
+ val integration = FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ settings,
+ toolbar,
+ mock(),
+ engineView,
+ mock(),
+ )
+
+ integration.exitBrowserFullscreen()
+
+ verify(toolbar, never()).showAsFixed(activity, engineView)
+ with(inOrder(toolbar)) {
+ verify(toolbar).enableDynamicBehavior(activity, engineView)
+ verify(toolbar).expand()
+ }
+ }
+
+ @Test
+ fun `WHEN entering fullscreen THEN put browser in fullscreen, hide system bars and enter immersive mode`() {
+ val toolbar = BrowserToolbar(testContext)
+ val engineView: GeckoEngineView = mock()
+ doReturn(mock()).`when`(engineView).asView()
+ val settings: Settings = mock()
+ doReturn(false).`when`(settings).isAccessibilityEnabled()
+ val activity = Robolectric.buildActivity(Activity::class.java).get()
+ val statusBar: View = mock()
+ val integration = spy(
+ FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ settings,
+ toolbar,
+ statusBar,
+ engineView,
+ mock(),
+ ),
+ )
+
+ val fullScreenNotification = mock()
+ integration.fullScreenChanged(true, fullScreenNotification)
+
+ verify(integration).enterBrowserFullscreen()
+ verify(statusBar).isVisible = false
+ verify(fullScreenNotification).show(any())
+ verify(integration).switchToImmersiveMode()
+ }
+
+ @Test
+ fun `WHEN exiting fullscreen THEN put browser in fullscreen, hide system bars and enter immersive mode`() {
+ val toolbar: BrowserToolbar = mock()
+ val engineView: GeckoEngineView = mock()
+ doReturn(mock()).`when`(engineView).asView()
+ val settings: Settings = mock()
+ doReturn(false).`when`(settings).isAccessibilityEnabled()
+ val resources: Resources = mock()
+ val activityWindow: Window = mock()
+ val decorView: View = mock()
+ val windowAttributes = WindowManager.LayoutParams()
+ val activity: Activity = mock()
+ doReturn(activityWindow).`when`(activity).window
+ doReturn(decorView).`when`(activityWindow).decorView
+ doReturn(windowAttributes).`when`(activityWindow).attributes
+ doReturn(resources).`when`(activity).resources
+ val statusBar: View = mock()
+ val integration = spy(
+ FullScreenIntegration(
+ activity,
+ mock(),
+ null,
+ mock(),
+ settings,
+ toolbar,
+ statusBar,
+ engineView,
+ mock(),
+ ),
+ )
+
+ integration.fullScreenChanged(false)
+
+ verify(integration).exitBrowserFullscreen()
+ verify(integration).exitImmersiveMode()
+ verify(statusBar).isVisible = true
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/InputToolbarIntegrationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/InputToolbarIntegrationTest.kt
new file mode 100644
index 0000000000..c2ac323e70
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/InputToolbarIntegrationTest.kt
@@ -0,0 +1,82 @@
+/* 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.focus.browser.integration
+
+import android.view.View
+import kotlinx.coroutines.isActive
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.support.test.ext.joinBlocking
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.test.whenever
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.fragment.UrlInputFragment
+import org.mozilla.focus.input.InputToolbarIntegration
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.AppStore
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class InputToolbarIntegrationTest {
+ private lateinit var toolbar: BrowserToolbar
+
+ @Mock
+ private lateinit var fragment: UrlInputFragment
+
+ @Mock
+ private lateinit var fragmentView: View
+
+ private lateinit var inputToolbarIntegration: InputToolbarIntegration
+
+ private val appStore: AppStore = testContext.components.appStore
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+
+ toolbar = BrowserToolbar(testContext)
+ whenever(fragment.resources).thenReturn(testContext.resources)
+ whenever(fragment.context).thenReturn(testContext)
+ whenever(fragment.view).thenReturn(fragmentView)
+
+ inputToolbarIntegration = InputToolbarIntegration(
+ toolbar,
+ fragment,
+ mock(),
+ mock(),
+ )
+ }
+
+ @Test
+ fun `GIVEN app fresh install WHEN input toolbar integration is starting THEN start browsing scope is populated`() {
+ appStore.dispatch(AppAction.ShowStartBrowsingCfrChange(true)).joinBlocking()
+
+ assertNull(inputToolbarIntegration.startBrowsingCfrScope)
+
+ inputToolbarIntegration.start()
+
+ assertNotNull(inputToolbarIntegration.startBrowsingCfrScope)
+ }
+
+ @Test
+ fun `GIVEN app fresh install WHEN input toolbar integration is stoping THEN start browsing scope is canceled`() {
+ inputToolbarIntegration.start()
+
+ assertTrue(inputToolbarIntegration.startBrowsingCfrScope?.isActive ?: true)
+
+ inputToolbarIntegration.stop()
+
+ assertFalse(inputToolbarIntegration.startBrowsingCfrScope?.isActive ?: false)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/cfr/CfrMiddlewareTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/cfr/CfrMiddlewareTest.kt
new file mode 100644
index 0000000000..72999a1748
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/cfr/CfrMiddlewareTest.kt
@@ -0,0 +1,127 @@
+/* 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.focus.cfr
+
+import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.action.TabListAction
+import mozilla.components.browser.state.action.TrackingProtectionAction
+import mozilla.components.browser.state.state.SecurityInfoState
+import mozilla.components.browser.state.state.TabSessionState
+import mozilla.components.browser.state.state.createTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.content.blocking.Tracker
+import mozilla.components.support.test.ext.joinBlocking
+import mozilla.components.support.test.libstate.ext.waitUntilIdle
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import org.mozilla.focus.TestFocusApplication
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.nimbus.FocusNimbus
+import org.mozilla.focus.nimbus.Onboarding
+import org.mozilla.focus.state.AppStore
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(application = TestFocusApplication::class)
+class CfrMiddlewareTest {
+ private lateinit var onboardingExperiment: Onboarding
+ private val browserStore: BrowserStore = testContext.components.store
+ private val appStore: AppStore = testContext.components.appStore
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+ onboardingExperiment = FocusNimbus.features.onboarding.value()
+ }
+
+ @Test
+ fun `GIVEN shouldShowCfrForTrackingProtection is true WHEN UpdateSecurityInfoAction is intercepted THEN showTrackingProtectionCfr is changed to true`() {
+ if (onboardingExperiment.isCfrEnabled) {
+ val updateSecurityInfoAction = ContentAction.UpdateSecurityInfoAction(
+ "1",
+ SecurityInfoState(
+ secure = true,
+ host = "test.org",
+ issuer = "Test",
+ ),
+ )
+ val trackerBlockedAction = TrackingProtectionAction.TrackerBlockedAction(
+ tabId = "1",
+ tracker = Tracker(
+ url = "test.org",
+ trackingCategories = listOf(EngineSession.TrackingProtectionPolicy.TrackingCategory.CRYPTOMINING),
+ cookiePolicies = listOf(EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE),
+ ),
+ )
+
+ browserStore.dispatch(updateSecurityInfoAction).joinBlocking()
+ browserStore.dispatch(trackerBlockedAction).joinBlocking()
+ appStore.waitUntilIdle()
+
+ assertTrue(appStore.state.showTrackingProtectionCfrForTab.getOrDefault("1", false))
+ }
+ }
+
+ @Test
+ fun `GIVEN insecure tab WHEN UpdateSecurityInfoAction is intercepted THEN showTrackingProtectionCfr is not changed to true`() {
+ if (onboardingExperiment.isCfrEnabled) {
+ val insecureTab = createTab(isSecure = false)
+ val updateSecurityInfoAction = ContentAction.UpdateSecurityInfoAction(
+ "1",
+ SecurityInfoState(
+ secure = false,
+ host = "test.org",
+ issuer = "Test",
+ ),
+ )
+
+ browserStore.dispatch(TabListAction.AddTabAction(insecureTab)).joinBlocking()
+ browserStore.dispatch(updateSecurityInfoAction).joinBlocking()
+ appStore.waitUntilIdle()
+
+ assertFalse(appStore.state.showTrackingProtectionCfrForTab.getOrDefault("1", false))
+ }
+ }
+
+ @Test
+ fun `GIVEN mozilla tab WHEN UpdateSecurityInfoAction is intercepted THEN showTrackingProtectionCfr is not changed to true`() {
+ if (onboardingExperiment.isCfrEnabled) {
+ val mozillaTab = createTab(id = "1", url = "https://www.mozilla.org")
+ val updateSecurityInfoAction = ContentAction.UpdateSecurityInfoAction(
+ "1",
+ SecurityInfoState(
+ secure = true,
+ host = "test.org",
+ issuer = "Test",
+ ),
+ )
+ browserStore.dispatch(TabListAction.AddTabAction(mozillaTab)).joinBlocking()
+ browserStore.dispatch(updateSecurityInfoAction).joinBlocking()
+ appStore.waitUntilIdle()
+
+ assertFalse(appStore.state.showTrackingProtectionCfrForTab.getOrDefault("1", false))
+ }
+ }
+
+ private fun createTab(
+ tabUrl: String = "https://www.test.org",
+ tabId: Int = 1,
+ isSecure: Boolean = true,
+ ): TabSessionState {
+ val tab = createTab(tabUrl, id = tabId.toString())
+ return tab.copy(
+ content = tab.content.copy(
+ private = true,
+ securityInfo = SecurityInfoState(secure = isSecure),
+ ),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/contextmenu/ContextMenuCandidatesTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/contextmenu/ContextMenuCandidatesTest.kt
new file mode 100644
index 0000000000..71ebb4ead3
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/contextmenu/ContextMenuCandidatesTest.kt
@@ -0,0 +1,81 @@
+/* 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.focus.contextmenu
+
+import android.content.Context
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+
+class ContextMenuCandidatesTest {
+ private val testContext: Context = mock()
+
+ @Before
+ fun setUp() {
+ whenever(testContext.getString(anyInt())).thenReturn("dummy label")
+ }
+
+ @Test
+ fun `WHEN the tab is a custom tab THEN the proper context candidates are created `() {
+ // the expected list is the same as in a Fenix custom tab.
+ val expectedCandidatesIDs = listOf(
+ "mozac.feature.contextmenu.copy_link",
+ "mozac.feature.contextmenu.share_link",
+ "mozac.feature.contextmenu.save_image",
+ "mozac.feature.contextmenu.save_video",
+ "mozac.feature.contextmenu.copy_image_location",
+ )
+
+ val actualListIDs = ContextMenuCandidates.get(
+ testContext,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ true,
+ ).map {
+ it.id
+ }
+
+ assertEquals(expectedCandidatesIDs, actualListIDs)
+ }
+
+ @Test
+ fun `WHEN the tab is NOT a custom tab THEN the proper context candidates are created `() {
+ val expectedCandidatesIDs = listOf(
+ "mozac.feature.contextmenu.open_in_private_tab",
+ "mozac.feature.contextmenu.copy_link",
+ "mozac.feature.contextmenu.download_link",
+ "mozac.feature.contextmenu.share_link",
+ "mozac.feature.contextmenu.share_image",
+ "mozac.feature.contextmenu.open_image_in_new_tab",
+ "mozac.feature.contextmenu.save_image",
+ "mozac.feature.contextmenu.save_video",
+ "mozac.feature.contextmenu.copy_image_location",
+ "mozac.feature.contextmenu.add_to_contact",
+ "mozac.feature.contextmenu.share_email",
+ "mozac.feature.contextmenu.copy_email_address",
+ "mozac.feature.contextmenu.open_in_external_app",
+ )
+
+ val actualListIDs = ContextMenuCandidates.get(
+ testContext,
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ false,
+ ).map {
+ it.id
+ }
+
+ assertEquals(expectedCandidatesIDs, actualListIDs)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/experiments/NimbusSetupTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/experiments/NimbusSetupTest.kt
new file mode 100644
index 0000000000..b63db61fa9
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/experiments/NimbusSetupTest.kt
@@ -0,0 +1,25 @@
+/* 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.focus.experiments
+
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.mozilla.experiments.nimbus.internal.NimbusException
+
+class NimbusSetupTest {
+ @Test
+ fun `WHEN error is reportable THEN return true`() {
+ val error = NimbusException.IoException("IOException")
+
+ assertTrue(error.isReportableError())
+ }
+
+ @Test
+ fun `WHEN error is non-reportable THEN return false`() {
+ val error = NimbusException.ClientException("ResponseException")
+
+ assertFalse(error.isReportableError())
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/BrowserToolbarTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/BrowserToolbarTest.kt
new file mode 100644
index 0000000000..4407e03a76
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/BrowserToolbarTest.kt
@@ -0,0 +1,80 @@
+/* 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.focus.ext
+
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.isVisible
+import mozilla.components.browser.engine.gecko.GeckoEngineView
+import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.ui.widgets.behavior.EngineViewClippingBehavior
+import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mozilla.focus.R
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+internal class BrowserToolbarTest {
+ private val parent = CoordinatorLayout(testContext)
+ private val toolbar = spy(BrowserToolbar(testContext))
+ private val engineView = spy(GeckoEngineView(testContext))
+ private val toolbarHeight = testContext.resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
+
+ init {
+ doReturn(toolbarHeight).`when`(toolbar).height
+ parent.addView(toolbar)
+ parent.addView(engineView)
+ }
+
+ @Test
+ fun `GIVEN a BrowserToolbar WHEN enableDynamicBehavior THEN set custom behaviors for the toolbar and engineView`() {
+ // Simulate previously having a fixed toolbar
+ (engineView.layoutParams as? CoordinatorLayout.LayoutParams)?.topMargin = 222
+
+ toolbar.enableDynamicBehavior(testContext, engineView)
+
+ assertTrue((toolbar.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior is EngineViewScrollingBehavior)
+ assertTrue((engineView.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior is EngineViewClippingBehavior)
+ assertEquals(0, (engineView.layoutParams as? CoordinatorLayout.LayoutParams)?.topMargin)
+ verify(engineView).setDynamicToolbarMaxHeight(toolbarHeight)
+ }
+
+ @Test
+ fun `GIVEN a BrowserToolbar WHEN disableDynamicBehavior THEN set null behaviors for the toolbar and engineView`() {
+ engineView.asView().translationY = 123f
+
+ toolbar.disableDynamicBehavior(engineView)
+
+ assertNull((toolbar.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior)
+ assertNull((engineView.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior)
+ assertEquals(0f, engineView.asView().translationY)
+ verify(engineView).setDynamicToolbarMaxHeight(0)
+ }
+
+ @Test
+ fun `GIVEN a BrowserToolbar WHEN showAsFixed is called THEN show the toolbar with the engineView below it`() {
+ toolbar.showAsFixed(testContext, engineView)
+
+ verify(toolbar).isVisible = true
+ verify(engineView).setDynamicToolbarMaxHeight(0)
+ assertEquals(toolbarHeight, (engineView.layoutParams as? CoordinatorLayout.LayoutParams)?.topMargin)
+ }
+
+ @Test
+ fun `GIVEN a BrowserToolbar WHEN hide is called THEN show the toolbar with the engineView below it`() {
+ toolbar.hide(engineView)
+
+ verify(toolbar).isVisible = false
+ verify(engineView).setDynamicToolbarMaxHeight(0)
+ assertEquals(0, (engineView.layoutParams as? CoordinatorLayout.LayoutParams)?.topMargin)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/StringTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/StringTest.kt
new file mode 100644
index 0000000000..33090def9e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/StringTest.kt
@@ -0,0 +1,80 @@
+/* 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.focus.ext
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class StringTest {
+ @Test
+ fun testBeautifyUrl() {
+ assertEqualsBeautified(
+ "wikipedia.org/wiki/Adler_Planetarium",
+ "https://en.m.wikipedia.org/wiki/Adler_Planetarium",
+ )
+
+ assertEqualsBeautified(
+ "youtube.com/watch?v=WXqGDW7kuAk",
+ "https://youtube.com/watch?v=WXqGDW7kuAk",
+ )
+
+ assertEqualsBeautified(
+ "spotify.com/album/6JVdzwuTEaLj7Tga8DpFpz",
+ "http://open.spotify.com/album/6JVdzwuTEaLj7Tga8DpFpz",
+ )
+
+ assertEqualsBeautified(
+ "google.com/mail/…/0#inbox/15e34774924ddfb5",
+ "https://mail.google.com/mail/u/0/#inbox/15e34774924ddfb5",
+ )
+
+ assertEqualsBeautified(
+ "google.com/store/…/details?id=com.facebook.katana",
+ "https://play.google.com/store/apps/details?id=com.facebook.katana&hl=en",
+ )
+
+ assertEqualsBeautified(
+ "amazon.com/Mockingjay-Hunger-Games-Suzanne-Collins/…/ref=pd_sim_14_2?_encoding=UTF8",
+ "http://amazon.com/Mockingjay-Hunger-Games-Suzanne-Collins/dp/0545663261/ref=pd_sim_14_2?_encoding=UTF8&psc=1&refRID=90ZHE3V976TKBGDR9VAM",
+ )
+
+ assertEqualsBeautified(
+ "usbank.com/Auth/Login",
+ "https://onlinebanking.usbank.com/Auth/Login",
+ )
+
+ assertEqualsBeautified(
+ "wsj.com/articles/mexican-presidential-candidate-calls-for-nafta-talks-to-be-suspended-1504137175",
+ "https://www.wsj.com/articles/mexican-presidential-candidate-calls-for-nafta-talks-to-be-suspended-1504137175",
+ )
+
+ assertEqualsBeautified(
+ "nytimes.com/2017/…/princess-diana-death-anniversary.html?hp",
+ "https://www.nytimes.com/2017/08/30/world/europe/princess-diana-death-anniversary.html?hp&action=click&pgtype=Homepage&clickSource=story-heading&module=second-column-region®ion=top-news&WT.nav=top-news",
+ )
+
+ assertEqualsBeautified(
+ "yahoo.co.jp/hl?a=20170830-00000008-jct-soci",
+ "https://headlines.yahoo.co.jp/hl?a=20170830-00000008-jct-soci",
+ )
+
+ assertEqualsBeautified(
+ "tomshardware.co.uk/answers/…/running-guest-network-channel-interference.html",
+ "http://www.tomshardware.co.uk/answers/id-2025922/running-guest-network-channel-interference.html",
+ )
+
+ assertEqualsBeautified(
+ "github.com/mozilla-mobile/…/1231",
+ "https://github.com/mozilla-mobile/focus-android/issues/1231",
+ )
+ }
+
+ private fun assertEqualsBeautified(expected: String, url: String) {
+ assertEquals("beautify($url)", expected, url.beautifyUrl())
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/UriTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/UriTest.kt
new file mode 100644
index 0000000000..8559f251c8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/ext/UriTest.kt
@@ -0,0 +1,82 @@
+/* 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.focus.ext
+
+import androidx.core.net.toUri
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class UriTest {
+ @Test
+ fun testTruncatedHostWithCommonUrls() {
+ assertTruncatedHost("mozilla.org", "https://www.mozilla.org")
+ assertTruncatedHost("wikipedia.org", "https://en.m.wikipedia.org/wiki/")
+ assertTruncatedHost("example.org", "http://example.org")
+ assertTruncatedHost("youtube.com", "https://www.youtube.com/watch?v=oHg5SJYRHA0")
+ assertTruncatedHost("facebook.com", "https://www.facebook.com/Firefox/")
+ assertTruncatedHost("yahoo.com", "https://de.search.yahoo.com/search?p=mozilla&fr=yfp-t&fp=1&toggle=1&cop=mss&ei=UTF-8")
+ assertTruncatedHost("amazon.co.uk", "https://www.amazon.co.uk/Doctor-Who-10-Part-DVD/dp/B06XCMVY1H")
+ }
+
+ @Test
+ fun testTruncatedHostWithEmptyHost() {
+ assertTruncatedHost("", "tel://")
+ }
+
+ @Test
+ fun testTruncatedPathWithEmptySegments() {
+ assertTruncatedPath("", "https://www.mozilla.org")
+ assertTruncatedPath("", "https://www.mozilla.org/")
+ assertTruncatedPath("", "https://www.mozilla.org///")
+ }
+
+ @Test
+ fun testTrunactedPathWithOneSegment() {
+ assertTruncatedPath("/space", "https://www.theverge.com/space")
+ }
+
+ @Test
+ fun testTruncatedPathWithTwoSegments() {
+ assertTruncatedPath("/en-US/firefox", "https://www.mozilla.org/en-US/firefox/")
+ assertTruncatedPath("/mozilla-mobile/focus-android", "https://github.com/mozilla-mobile/focus-android")
+ }
+
+ @Test
+ fun testTruncatedPathWithMultipleSegments() {
+ assertTruncatedPath("/en-US/…/fast", "https://www.mozilla.org/en-US/firefox/features/fast/")
+
+ assertTruncatedPath(
+ "/2017/…/nasa-hi-seas-mars-analogue-mission-hawaii-mauna-loa",
+ "https://www.theverge.com/2017/9/24/16356876/nasa-hi-seas-mars-analogue-mission-hawaii-mauna-loa",
+ )
+ }
+
+ @Test
+ fun testTruncatedPathWithMultipleSegmentsAndFragment() {
+ assertTruncatedPath(
+ "/@bfrancis/the-story-of-firefox-os-cb5bf796e8fb",
+ "https://medium.com/@bfrancis/the-story-of-firefox-os-cb5bf796e8fb#931a",
+ )
+ }
+
+ private fun assertTruncatedHost(expectedTruncatedPath: String, url: String) {
+ assertEquals(
+ "truncatedHost($url)",
+ expectedTruncatedPath,
+ url.toUri().truncatedHost(),
+ )
+ }
+
+ private fun assertTruncatedPath(expectedTruncatedPath: String, url: String) {
+ assertEquals(
+ "truncatedPath($url)",
+ expectedTruncatedPath,
+ url.toUri().truncatedPath(),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/locale/LocalesTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/locale/LocalesTest.kt
new file mode 100644
index 0000000000..7a7f37d14a
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/locale/LocalesTest.kt
@@ -0,0 +1,46 @@
+/* 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.focus.locale
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.mozilla.focus.locale.Locales.getLanguage
+import org.mozilla.focus.locale.Locales.getLanguageTag
+import java.util.Locale
+
+class LocalesTest {
+ @Test
+ fun testLanguage() {
+ assertEquals("en", getLanguage(Locale.getDefault()))
+ }
+
+ @Test
+ fun testHebrewIsrael() {
+ val locale = Locale("iw", "IL")
+ assertEquals("he", getLanguage(locale))
+ assertEquals("he-IL", getLanguageTag(locale))
+ }
+
+ @Test
+ fun testIndonesianIndonesia() {
+ val locale = Locale("in", "ID")
+ assertEquals("id", getLanguage(locale))
+ assertEquals("id-ID", getLanguageTag(locale))
+ }
+
+ @Test
+ fun testYiddishUnitedStates() {
+ val locale = Locale("ji", "US")
+ assertEquals("yi", getLanguage(locale))
+ assertEquals("yi-US", getLanguageTag(locale))
+ }
+
+ @Test
+ fun testEmptyCountry() {
+ val locale = Locale("en")
+ assertEquals("en", getLanguage(locale))
+ assertEquals("en", getLanguageTag(locale))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/menu/BrowserMenuControllerTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/menu/BrowserMenuControllerTest.kt
new file mode 100644
index 0000000000..a5547b617d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/menu/BrowserMenuControllerTest.kt
@@ -0,0 +1,162 @@
+/* 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.focus.menu
+
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.state.createTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.top.sites.TopSitesUseCases
+import mozilla.components.support.test.any
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.MockitoAnnotations
+import org.mockito.Spy
+import org.mozilla.focus.browser.integration.BrowserMenuController
+import org.mozilla.focus.state.AppStore
+
+class BrowserMenuControllerTest {
+ private lateinit var browserMenuController: BrowserMenuController
+
+ @Spy
+ private lateinit var sessionUseCases: SessionUseCases
+
+ @Mock
+ private lateinit var appStore: AppStore
+
+ @Mock
+ private lateinit var topSitesUseCases: TopSitesUseCases
+
+ private val currentTabId: String = "1"
+ private val selectedTab = createTab("https://www.mozilla.org", id = "1")
+ private val shareCallback: () -> Unit = {}
+
+ @Mock
+ private lateinit var requestDesktopCallback: (isChecked: Boolean) -> Unit
+
+ @Mock
+ private lateinit var addToHomeScreenCallback: () -> Unit
+
+ @Mock
+ private lateinit var showFindInPageCallback: () -> Unit
+
+ @Mock
+ private lateinit var openInCallback: () -> Unit
+
+ // NB: we should avoid mocking lambdas..
+ @Mock
+ private lateinit var openInBrowser: () -> Unit
+
+ @Mock
+ private lateinit var showShortcutAddedSnackBar: () -> Unit
+
+ @Before
+ fun setup() {
+ val store = BrowserStore(
+ initialState = BrowserState(
+ tabs = listOf(selectedTab),
+ selectedTabId = selectedTab.id,
+ ),
+ )
+ sessionUseCases = SessionUseCases(store)
+ MockitoAnnotations.openMocks(this)
+
+ browserMenuController = spy(
+ BrowserMenuController(
+ sessionUseCases,
+ appStore,
+ store,
+ topSitesUseCases,
+ currentTabId,
+ shareCallback,
+ requestDesktopCallback,
+ addToHomeScreenCallback,
+ showFindInPageCallback,
+ openInCallback,
+ openInBrowser,
+ showShortcutAddedSnackBar,
+ ),
+ )
+
+ doNothing().`when`(browserMenuController).recordBrowserMenuTelemetry(any())
+ }
+
+ @Test
+ fun `GIVEN Back menu item WHEN the item is pressed THEN goBack use case is called`() {
+ val menuItem = ToolbarMenu.Item.Back
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(sessionUseCases, times(1)).goBack
+ }
+
+ @Test
+ fun `GIVEN Forward menu item WHEN the item is pressed THEN goForward use case is called`() {
+ val menuItem = ToolbarMenu.Item.Forward
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(sessionUseCases, times(1)).goForward
+ }
+
+ @Test
+ fun `GIVEN Reload menu item WHEN the item is pressed THEN reload use case is called`() {
+ val menuItem = ToolbarMenu.Item.Reload
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(sessionUseCases, times(1)).reload
+ }
+
+ @Test
+ fun `GIVEN Stop menu item WHEN the item is pressed THEN stopLoading use case is called`() {
+ val menuItem = ToolbarMenu.Item.Stop
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(sessionUseCases, times(1)).stopLoading
+ }
+
+ @Test
+ @Suppress("MaxLineLength")
+ fun `GIVEN RequestDesktop menu item WHEN the item is switched to false THEN requestDesktopCallback with false is called`() {
+ val menuItem = ToolbarMenu.Item.RequestDesktop(isChecked = false)
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(requestDesktopCallback, times(1)).invoke(false)
+ }
+
+ @Test
+ @Suppress("MaxLineLength")
+ fun `GIVEN RequestDesktop menu item WHEN the item is switched to true THEN requestDesktopCallback with true is called`() {
+ val menuItem = ToolbarMenu.Item.RequestDesktop(isChecked = true)
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(requestDesktopCallback, times(1)).invoke(true)
+ }
+
+ @Test
+ fun `GIVEN OpenInBrowser menu item WHEN the item is pressed THEN openInBrowser is called`() {
+ val menuItem = ToolbarMenu.CustomTabItem.OpenInBrowser
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(openInBrowser, times(1)).invoke()
+ }
+
+ @Test
+ fun `GIVEN OpenIn menu item WHEN the item is pressed THEN openInCallback is called`() {
+ val menuItem = ToolbarMenu.Item.OpenInApp
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(openInCallback, times(1)).invoke()
+ }
+
+ @Test
+ fun `GIVEN FindInPage menu item WHEN the item is pressed THEN findInPageMenuEvent method is called`() {
+ val menuItem = ToolbarMenu.Item.FindInPage
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(showFindInPageCallback, times(1)).invoke()
+ }
+
+ @Test
+ fun `Given AddToShortCut menu item WHEN the item is pressed THEN showShortcutAddedSnackBar is called`() {
+ val menuItem = ToolbarMenu.Item.AddToShortcuts
+ browserMenuController.handleMenuInteraction(menuItem)
+ Mockito.verify(showShortcutAddedSnackBar, times(1)).invoke()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingControllerTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingControllerTest.kt
new file mode 100644
index 0000000000..5d02674913
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingControllerTest.kt
@@ -0,0 +1,72 @@
+/* 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.focus.onboarding
+
+import android.os.Build
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mozilla.focus.R
+import org.mozilla.focus.fragment.onboarding.DefaultOnboardingController
+import org.mozilla.focus.fragment.onboarding.OnboardingController
+import org.mozilla.focus.fragment.onboarding.OnboardingStorage
+import org.mozilla.focus.state.AppStore
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+class OnboardingControllerTest {
+
+ @Mock
+ private lateinit var appStore: AppStore
+
+ @Mock
+ private lateinit var onboardingStorage: OnboardingStorage
+ private lateinit var onboardingController: OnboardingController
+
+ @Before
+ fun init() {
+ MockitoAnnotations.openMocks(this)
+ onboardingController = spy(
+ DefaultOnboardingController(
+ onboardingStorage,
+ appStore,
+ ApplicationProvider.getApplicationContext(),
+ "1",
+ ),
+ )
+ }
+
+ @Test
+ fun `GIVEN onBoarding, WHEN start browsing is pressed, THEN onBoarding flag is true`() {
+ DefaultOnboardingController(
+ onboardingStorage,
+ appStore,
+ ApplicationProvider.getApplicationContext(),
+ "1",
+ ).handleFinishOnBoarding()
+
+ val prefManager =
+ PreferenceManager.getDefaultSharedPreferences(ApplicationProvider.getApplicationContext())
+
+ assertEquals(false, prefManager.getBoolean(testContext.getString(R.string.firstrun_shown), false))
+ }
+
+ @Config(sdk = [Build.VERSION_CODES.M])
+ @Test
+ fun `GIVEN onBoarding and build version is M, WHEN get started button is pressed, THEN onBoarding flow must end`() {
+ onboardingController.handleGetStartedButtonClicked()
+
+ verify(onboardingController, times(1)).handleFinishOnBoarding()
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingStorageTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingStorageTest.kt
new file mode 100644
index 0000000000..39d9f8c603
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/onboarding/OnboardingStorageTest.kt
@@ -0,0 +1,79 @@
+/* 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.focus.onboarding
+
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
+import org.mozilla.focus.R
+import org.mozilla.focus.fragment.onboarding.OnboardingStep
+import org.mozilla.focus.fragment.onboarding.OnboardingStorage
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class OnboardingStorageTest {
+ private lateinit var onBoardingStorage: OnboardingStorage
+
+ @Before
+ fun setup() {
+ onBoardingStorage = spy(OnboardingStorage(testContext))
+ }
+
+ @Test
+ fun `GIVEN at the first onboarding step WHEN querying the current onboarding step from storage THEN get the first step`() {
+ doReturn(testContext.getString(R.string.pref_key_first_screen))
+ .`when`(onBoardingStorage).getCurrentOnboardingStepFromSharedPref()
+
+ assertEquals(
+ OnboardingStep.ON_BOARDING_FIRST_SCREEN,
+ onBoardingStorage.getCurrentOnboardingStep(),
+ )
+ }
+
+ @Test
+ fun `GIVEN at the second onboarding step WHEN querying the current onboarding step from storage THEN get the second step`() {
+ doReturn(testContext.getString(R.string.pref_key_second_screen))
+ .`when`(onBoardingStorage).getCurrentOnboardingStepFromSharedPref()
+
+ assertEquals(
+ OnboardingStep.ON_BOARDING_SECOND_SCREEN,
+ onBoardingStorage.getCurrentOnboardingStep(),
+ )
+ }
+
+ @Test
+ fun `GIVEN onboarding not started WHEN querying the current onboarding step from storage THEN get the first step`() {
+ assertEquals(
+ OnboardingStep.ON_BOARDING_FIRST_SCREEN,
+ onBoardingStorage.getCurrentOnboardingStep(),
+ )
+ }
+
+ @Test
+ fun `GIVEN saveCurrentOnBoardingStepInSharePref is called WHEN ONBOARDING_FIRST_SCREEN is saved, THEN value for pref_key_onboarding_step is pref_key_first_screen`() {
+ onBoardingStorage.saveCurrentOnboardingStepInSharePref(OnboardingStep.ON_BOARDING_FIRST_SCREEN)
+
+ val prefManager =
+ PreferenceManager.getDefaultSharedPreferences(ApplicationProvider.getApplicationContext())
+
+ assertEquals(testContext.getString(OnboardingStep.ON_BOARDING_FIRST_SCREEN.prefId), prefManager.getString(testContext.getString(R.string.pref_key_onboarding_step), ""))
+ }
+
+ @Test
+ fun `GIVEN saveCurrentOnBoardingStepInSharePref is called WHEN ONBOARDING_SECOND_SCREEN is saved, THEN value for pref_key_onboarding_step is pref_key_second_screen`() {
+ onBoardingStorage.saveCurrentOnboardingStepInSharePref(OnboardingStep.ON_BOARDING_SECOND_SCREEN)
+
+ val prefManager =
+ PreferenceManager.getDefaultSharedPreferences(ApplicationProvider.getApplicationContext())
+
+ assertEquals(testContext.getString(OnboardingStep.ON_BOARDING_SECOND_SCREEN.prefId), prefManager.getString(testContext.getString(R.string.pref_key_onboarding_step), ""))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModelTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModelTest.kt
new file mode 100644
index 0000000000..c2c686c8e0
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchsuggestions/SearchSuggestionsViewModelTest.kt
@@ -0,0 +1,59 @@
+/* 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.focus.searchsuggestions
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.Observer
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SearchSuggestionsViewModelTest {
+ @get:Rule
+ var rule: TestRule = InstantTaskExecutorRule()
+
+ @Mock
+ private lateinit var observer: Observer
+
+ private lateinit var lifecycle: LifecycleRegistry
+ private lateinit var viewModel: SearchSuggestionsViewModel
+
+ @Before
+ fun setup() {
+ lifecycle = LifecycleRegistry(mock(LifecycleOwner::class.java))
+ MockitoAnnotations.openMocks(this)
+
+ viewModel = SearchSuggestionsViewModel(ApplicationProvider.getApplicationContext())
+ }
+
+ @Test
+ fun setSearchQuery() {
+ viewModel.searchQuery.observeForever(observer)
+
+ viewModel.setSearchQuery("Mozilla")
+ verify(observer).onChanged("Mozilla")
+ }
+
+ @Test
+ fun alwaysSearchSelected() {
+ viewModel.selectedSearchSuggestion.observeForever(observer)
+
+ viewModel.selectSearchSuggestion("mozilla.com", "google", true)
+ verify(observer).onChanged("mozilla.com")
+ assertEquals(true, viewModel.alwaysSearch)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/ExternalIntentNavigationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/ExternalIntentNavigationTest.kt
new file mode 100644
index 0000000000..c1156022bd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/ExternalIntentNavigationTest.kt
@@ -0,0 +1,203 @@
+/* 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.focus.searchwidget
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import mozilla.components.browser.state.selector.allTabs
+import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
+import mozilla.components.browser.state.selector.privateTabs
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
+import mozilla.components.support.test.libstate.ext.waitUntilIdle
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.telemetry.glean.testing.GleanTestRule
+import org.junit.Assert.assertNotNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.runner.RunWith
+import org.mozilla.focus.GleanMetrics.SearchWidget
+import org.mozilla.focus.TestFocusApplication
+import org.mozilla.focus.activity.IntentReceiverActivity
+import org.mozilla.focus.ext.components
+import org.mozilla.focus.ext.settings
+import org.mozilla.focus.perf.Performance
+import org.mozilla.focus.state.AppAction
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.utils.SearchUtils
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.Implementation
+import org.robolectric.annotation.Implements
+
+@RunWith(RobolectricTestRunner::class)
+@Config(application = TestFocusApplication::class)
+internal class ExternalIntentNavigationTest {
+ @get:Rule
+ val gleanTestRule = GleanTestRule(testContext)
+
+ private val activity: Activity = Robolectric.buildActivity(Activity::class.java).setup().get()
+ private val appStore = activity.components.appStore
+
+ @Test
+ fun `GIVEN the onboarding should be shown and the app is not used in performance tests WHEN the app is opened THEN show the onboarding`() {
+ activity.settings.isFirstRun = true
+
+ ExternalIntentNavigation.handleAppOpened(null, activity)
+ appStore.waitUntilIdle()
+
+ assertEquals(Screen.FirstRun, appStore.state.screen)
+ }
+
+ @Test
+ @Config(shadows = [ShadowPerformance::class])
+ fun `GIVEN the onboarding should be shown and the app is used in a performance test WHEN the app is opened THEN show the homescreen`() {
+ // The AppStore is initialized before the test runs. By default isFirstRun is true. Simulate it being false.
+ appStore.dispatch(AppAction.ShowHomeScreen)
+ appStore.waitUntilIdle()
+ activity.settings.isFirstRun = true
+
+ ExternalIntentNavigation.handleAppOpened(null, activity)
+ appStore.waitUntilIdle()
+
+ assertEquals(Screen.Home, appStore.state.screen)
+ }
+
+ @Test
+ @Config(shadows = [ShadowPerformance::class])
+ fun `GIVEN the onboarding should not be shown and in a performance test WHEN the app is opened THEN show the home screen`() {
+ // The AppStore is initialized before the test runs. By default isFirstRun is true. Simulate it being false.
+ appStore.dispatch(AppAction.ShowHomeScreen)
+ appStore.waitUntilIdle()
+ activity.settings.isFirstRun = false
+
+ ExternalIntentNavigation.handleAppOpened(null, activity)
+ appStore.waitUntilIdle()
+
+ assertEquals(Screen.Home, appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN the onboarding should not be shown and not in a performance test WHEN the app is opened THEN show the home screen`() {
+ // The AppStore is initialized before the test runs. By default isFirstRun is true. Simulate it being false.
+ appStore.dispatch(AppAction.ShowHomeScreen)
+ appStore.waitUntilIdle()
+ activity.settings.isFirstRun = false
+
+ ExternalIntentNavigation.handleAppOpened(null, activity)
+ appStore.waitUntilIdle()
+
+ assertEquals(Screen.Home, appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN a tab is already open WHEN trying to navigate to the current tab THEN navigate to it and return true`() {
+ activity.components.tabsUseCases.addTab(url = "https://mozilla.com")
+ activity.components.store.waitUntilIdle()
+ val result = ExternalIntentNavigation.handleBrowserTabAlreadyOpen(activity)
+ activity.components.appStore.waitUntilIdle()
+
+ assertTrue(result)
+ val selectedTabId = activity.components.store.state.selectedTabId!!
+ assertEquals(Screen.Browser(selectedTabId, false), activity.components.appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN no tabs are currently open WHEN trying to navigate to the current tab THEN navigate home and return false`() {
+ // The AppStore is initialized before the test runs. By default isFirstRun is true. Simulate it being false.
+ appStore.dispatch(AppAction.ShowHomeScreen)
+ appStore.waitUntilIdle()
+ val result = ExternalIntentNavigation.handleBrowserTabAlreadyOpen(activity)
+ activity.components.appStore.waitUntilIdle()
+
+ assertFalse(result)
+ assertEquals(Screen.Home, activity.components.appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN a text search from the search widget WHEN handling widget interactions THEN record telemetry, show the home screen and return true`() {
+ val bundle = Bundle().apply {
+ putBoolean(IntentReceiverActivity.SEARCH_WIDGET_EXTRA, true)
+ }
+
+ val result = ExternalIntentNavigation.handleWidgetTextSearch(bundle, activity)
+ appStore.waitUntilIdle()
+
+ assertTrue(result)
+ assertNotNull(SearchWidget.newTabButton.testGetValue())
+ assertEquals(Screen.Home, appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN no text search from the search widget WHEN handling widget interactions THEN don't record telemetry, show the home screen and false true`() {
+ // The AppStore is initialized before the test runs. By default isFirstRun is true. Simulate it being false.
+ appStore.dispatch(AppAction.ShowHomeScreen)
+ appStore.waitUntilIdle()
+ val bundle = Bundle()
+
+ val result = ExternalIntentNavigation.handleWidgetTextSearch(bundle, activity)
+ appStore.waitUntilIdle()
+
+ assertFalse(result)
+ assertNull(SearchWidget.newTabButton.testGetValue())
+ assertEquals(Screen.Home, appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN a voice search WHEN handling widget interactions THEN create and open a new tab and return true`() {
+ val browserStore = activity.components.store
+ val searchArgument = "test"
+ val bundle = Bundle().apply {
+ putString(BaseVoiceSearchActivity.SPEECH_PROCESSING, searchArgument)
+ }
+
+ val result = ExternalIntentNavigation.handleWidgetVoiceSearch(bundle, activity)
+
+ assertTrue(result)
+ browserStore.waitUntilIdle()
+ assertEquals(1, browserStore.state.allTabs.size)
+ assertEquals(1, browserStore.state.privateTabs.size)
+ val voiceSearchTab = browserStore.state.privateTabs[0]
+ assertEquals(voiceSearchTab, browserStore.state.findCustomTabOrSelectedTab())
+ assertEquals(SearchUtils.createSearchUrl(activity, searchArgument), voiceSearchTab.content.url)
+ assertEquals(SessionState.Source.External.ActionSend(null), voiceSearchTab.source)
+ assertEquals(searchArgument, voiceSearchTab.content.searchTerms)
+ appStore.waitUntilIdle()
+ assertEquals(Screen.Browser(voiceSearchTab.id, false), appStore.state.screen)
+ }
+
+ @Test
+ fun `GIVEN no voice search WHEN handling widget interactions THEN don't open a new tab and return false`() {
+ // The AppStore is initialized before the test runs. By default isFirstRun is true. Simulate it being false.
+ appStore.dispatch(AppAction.ShowHomeScreen)
+ appStore.waitUntilIdle()
+ val browserStore = activity.components.store
+ val bundle = Bundle()
+
+ val result = ExternalIntentNavigation.handleWidgetVoiceSearch(bundle, activity)
+
+ assertFalse(result)
+ browserStore.waitUntilIdle()
+ assertEquals(0, browserStore.state.allTabs.size)
+ appStore.waitUntilIdle()
+ assertEquals(Screen.Home, appStore.state.screen)
+ }
+}
+
+/**
+ * Shadow of [Performance] that will have [processIntentIfPerformanceTest] always return `true`.
+ */
+@Implements(Performance::class)
+class ShadowPerformance {
+ @Implementation
+ @Suppress("Unused_Parameter")
+ fun processIntentIfPerformanceTest(bundle: Bundle?, context: Context) = true
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/SearchWidgetProviderTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/SearchWidgetProviderTest.kt
new file mode 100644
index 0000000000..6ad9a7b3b5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/searchwidget/SearchWidgetProviderTest.kt
@@ -0,0 +1,65 @@
+/* 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.focus.searchwidget
+
+import android.app.PendingIntent
+import android.content.Intent
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.utils.PendingIntentUtils
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mozilla.focus.activity.IntentReceiverActivity
+import org.mozilla.focus.ext.components
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SearchWidgetProviderTest {
+
+ private lateinit var searchWidgetProvider: SearchWidgetProvider
+
+ @Before
+ fun setup() {
+ searchWidgetProvider = spy(SearchWidgetProvider())
+ }
+
+ @Test
+ fun `GIVEN search widget provider WHEN onEnabled is called THEN searchWidgetInstalled from Settings should return true`() {
+ searchWidgetProvider.onEnabled(testContext)
+
+ assertEquals(testContext.components.settings.searchWidgetInstalled, true)
+ }
+
+ @Test
+ fun `GIVEN search widget provider WHEN onDeleted is called THEN searchWidgetInstalled from Settings should return false`() {
+ searchWidgetProvider.onDeleted(testContext, intArrayOf(1))
+
+ assertEquals(testContext.components.settings.searchWidgetInstalled, false)
+ }
+
+ @Test
+ fun `GIVEN search widget provider WHEN createTextSearchIntent is called THEN an PendingIntent should be return`() {
+ val textSearchIntent = Intent(testContext, IntentReceiverActivity::class.java)
+ .apply {
+ this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ this.putExtra(IntentReceiverActivity.SEARCH_WIDGET_EXTRA, true)
+ }
+ val dummyPendingIntent = PendingIntent.getActivity(
+ testContext,
+ SearchWidgetProvider.REQUEST_CODE_NEW_TAB,
+ textSearchIntent,
+ PendingIntentUtils.defaultFlags or
+ PendingIntent.FLAG_UPDATE_CURRENT,
+ )
+
+ assertEquals(searchWidgetProvider.createTextSearchIntent(testContext), dummyPendingIntent)
+ }
+
+ @Test
+ fun `GIVEN search widget provider WHEN voiceSearchActivity is called THEN VoiceSearchActivity should be return`() {
+ assertEquals(searchWidgetProvider.voiceSearchActivity(), VoiceSearchActivity::class.java)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/settings/SearchEngineValidationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/settings/SearchEngineValidationTest.kt
new file mode 100644
index 0000000000..07c032b5bd
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/settings/SearchEngineValidationTest.kt
@@ -0,0 +1,94 @@
+/* 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.focus.settings
+
+import mozilla.components.concept.fetch.Client
+import mozilla.components.concept.fetch.Request
+import mozilla.components.concept.fetch.Response
+import mozilla.components.lib.fetch.okhttp.OkHttpClient
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.settings.ManualAddSearchEngineSettingsFragment.Companion.isValidSearchQueryURL
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+// This unit test is not running on an Android device. Allow me to use spaces in function names.
+@Suppress("IllegalIdentifier")
+class SearchEngineValidationTest {
+
+ lateinit var client: Client
+
+ @Before
+ fun setup() {
+ client = OkHttpWrapper()
+ }
+
+ @Test
+ fun `URL returning 200 OK is valid`() = withMockWebServer(responseWithStatus(200)) {
+ assertTrue(isValidSearchQueryURL(client, it.rootUrl()))
+ }
+
+ @Test
+ fun `URL using HTTP redirect is invalid`() = withMockWebServer(responseWithStatus(301)) {
+ // We now follow redirects(Issue #1976). This test now asserts false.
+ assertFalse(isValidSearchQueryURL(client, it.rootUrl()))
+ }
+
+ @Test
+ fun `URL returning 404 NOT FOUND is not valid`() = withMockWebServer(responseWithStatus(404)) {
+ assertFalse(isValidSearchQueryURL(client, it.rootUrl()))
+ }
+
+ @Test
+ fun `URL returning server error is not valid`() = withMockWebServer(responseWithStatus(500)) {
+ assertFalse(isValidSearchQueryURL(client, it.rootUrl()))
+ }
+
+ @Test
+ fun `URL timing out is not valid`() = withMockWebServer {
+ // Without queuing a response MockWebServer will not return anything and keep the connection open
+ assertFalse(isValidSearchQueryURL(client, it.rootUrl()))
+ }
+}
+
+/**
+ * Helper for creating a test that uses a mock webserver instance.
+ */
+private fun withMockWebServer(vararg responses: MockResponse, block: (MockWebServer) -> Unit) {
+ val server = MockWebServer()
+
+ responses.forEach { server.enqueue(it) }
+
+ server.start()
+
+ try {
+ block(server)
+ } finally {
+ server.shutdown()
+ }
+}
+
+private fun MockWebServer.rootUrl(): String = url("/").toString()
+
+private fun responseWithStatus(status: Int) =
+ MockResponse()
+ .setResponseCode(status)
+ .setBody("")
+
+private class OkHttpWrapper : Client() {
+ private val actual = OkHttpClient()
+
+ override fun fetch(request: Request): Response {
+ // OkHttpClient does not support private requests. Therefore we make them non-private for
+ // testing purposes
+ val nonPrivate = request.copy(private = false)
+ return actual.fetch(nonPrivate)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/HomeScreenTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/HomeScreenTest.kt
new file mode 100644
index 0000000000..7cb9e889f7
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/HomeScreenTest.kt
@@ -0,0 +1,20 @@
+/* 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.focus.shortcut
+
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.focus.shortcut.HomeScreen.generateTitleFromUrl
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class HomeScreenTest {
+ @Test
+ fun testGenerateTitleFromUrl() {
+ assertEquals("mozilla.org", generateTitleFromUrl("https://www.mozilla.org"))
+ assertEquals("facebook.com", generateTitleFromUrl("http://m.facebook.com/home"))
+ assertEquals("", generateTitleFromUrl("mozilla"))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/IconGeneratorTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/IconGeneratorTest.kt
new file mode 100644
index 0000000000..f84c3d4d5d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/shortcut/IconGeneratorTest.kt
@@ -0,0 +1,59 @@
+/* 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.focus.shortcut
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class IconGeneratorTest {
+ @Test
+ fun testRepresentativeCharacter() {
+ assertEquals('M', IconGenerator.getRepresentativeCharacter("https://mozilla.org"))
+ assertEquals('W', IconGenerator.getRepresentativeCharacter("http://wikipedia.org"))
+ assertEquals('P', IconGenerator.getRepresentativeCharacter("http://plus.google.com"))
+ assertEquals('E', IconGenerator.getRepresentativeCharacter("https://en.m.wikipedia.org/wiki/Main_Page"))
+
+ // Stripping common prefixes
+ assertEquals('T', IconGenerator.getRepresentativeCharacter("http://www.theverge.com"))
+ assertEquals('F', IconGenerator.getRepresentativeCharacter("https://m.facebook.com"))
+ assertEquals('T', IconGenerator.getRepresentativeCharacter("https://mobile.twitter.com"))
+
+ // Special urls
+ assertEquals('?', IconGenerator.getRepresentativeCharacter("file:///"))
+ assertEquals('S', IconGenerator.getRepresentativeCharacter("file:///system/"))
+ assertEquals('P', IconGenerator.getRepresentativeCharacter("ftp://people.mozilla.org/test"))
+
+ // No values
+ assertEquals('?', IconGenerator.getRepresentativeCharacter(""))
+ assertEquals('?', IconGenerator.getRepresentativeCharacter(null))
+
+ // Rubbish
+ assertEquals('Z', IconGenerator.getRepresentativeCharacter("zZz"))
+ assertEquals('Ö', IconGenerator.getRepresentativeCharacter("ölkfdpou3rkjaslfdköasdfo8"))
+ assertEquals('?', IconGenerator.getRepresentativeCharacter("_*+*'##"))
+ assertEquals('ツ', IconGenerator.getRepresentativeCharacter("¯\\_(ツ)_/¯"))
+ assertEquals('ಠ', IconGenerator.getRepresentativeCharacter("ಠ_ಠ Look of Disapproval"))
+
+ // Non-ASCII
+ assertEquals('Ä', IconGenerator.getRepresentativeCharacter("http://www.ätzend.de"))
+ assertEquals('名', IconGenerator.getRepresentativeCharacter("http://名がドメイン.com"))
+ assertEquals('C', IconGenerator.getRepresentativeCharacter("http://√.com"))
+ assertEquals('ß', IconGenerator.getRepresentativeCharacter("http://ß.de"))
+ assertEquals('Ԛ', IconGenerator.getRepresentativeCharacter("http://ԛәлп.com/")) // cyrillic
+
+ // Punycode
+ assertEquals('X', IconGenerator.getRepresentativeCharacter("http://xn--tzend-fra.de")) // ätzend.de
+ assertEquals('X', IconGenerator.getRepresentativeCharacter("http://xn--V8jxj3d1dzdz08w.com")) // 名がドメイン.com
+
+ // Numbers
+ assertEquals('1', IconGenerator.getRepresentativeCharacter("https://www.1and1.com/"))
+
+ // IP
+ assertEquals('1', IconGenerator.getRepresentativeCharacter("https://192.168.0.1"))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStorageTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStorageTest.kt
new file mode 100644
index 0000000000..80168da22d
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStorageTest.kt
@@ -0,0 +1,342 @@
+/* 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.focus.sitepermissions
+
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
+import org.mozilla.focus.R
+import org.mozilla.focus.settings.permissions.AutoplayOption
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsStorage
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SitePermissionOptionsStorageTest {
+ private lateinit var storage: SitePermissionOptionsStorage
+
+ @Before
+ fun setup() {
+ storage = spy(SitePermissionOptionsStorage(testContext))
+ }
+
+ @Test
+ fun `GIVEN get site permission option selected label is called WHEN camera permission isn't granted THEN blocked by android is return`() {
+ doReturn(false).`when`(storage).isAndroidPermissionGranted(SitePermission.CAMERA)
+
+ assertEquals(
+ testContext.getString(R.string.phone_feature_blocked_by_android),
+ storage.getSitePermissionOptionSelectedLabel(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission option selected label is called WHEN camera permission is granted THEN default value ask to allow option is return`() {
+ doReturn(true).`when`(storage).isAndroidPermissionGranted(SitePermission.CAMERA)
+
+ assertEquals(
+ testContext.getString(R.string.preference_option_phone_feature_ask_to_allow),
+ storage.getSitePermissionOptionSelectedLabel(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission option selected label is called WHEN autoplay permission is granted THEN default value block audio only return`() {
+ doReturn(true).`when`(storage).isAndroidPermissionGranted(SitePermission.AUTOPLAY)
+
+ assertEquals(
+ testContext.getString(R.string.preference_block_autoplay_audio_only),
+ storage.getSitePermissionOptionSelectedLabel(SitePermission.AUTOPLAY),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission option selected label is called WHEN location permission is granted THEN default value ask to allow option is return`() {
+ doReturn(true).`when`(storage).isAndroidPermissionGranted(SitePermission.LOCATION)
+
+ assertEquals(
+ testContext.getString(R.string.preference_option_phone_feature_ask_to_allow),
+ storage.getSitePermissionOptionSelectedLabel(SitePermission.LOCATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission options is called WHEN location permission is passed as argument THEN available options are return`() {
+ assertEquals(
+ listOf(SitePermissionOption.AskToAllow(), SitePermissionOption.Blocked()),
+ storage.getSitePermissionOptions(SitePermission.LOCATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission options is called WHEN camera permission is passed as argument THEN available options are return`() {
+ assertEquals(
+ listOf(SitePermissionOption.AskToAllow(), SitePermissionOption.Blocked()),
+ storage.getSitePermissionOptions(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission options is called WHEN microphone permission is passed as argument THEN available options are return`() {
+ assertEquals(
+ listOf(SitePermissionOption.AskToAllow(), SitePermissionOption.Blocked()),
+ storage.getSitePermissionOptions(SitePermission.MICROPHONE),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission options is called WHEN notification permission is passed as argument THEN available options are return`() {
+ assertEquals(
+ listOf(SitePermissionOption.AskToAllow(), SitePermissionOption.Blocked()),
+ storage.getSitePermissionOptions(SitePermission.NOTIFICATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission options is called WHEN media key system access permission is passed as argument THEN available options are return`() {
+ assertEquals(
+ listOf(SitePermissionOption.AskToAllow(), SitePermissionOption.Blocked(), SitePermissionOption.Allowed()),
+ storage.getSitePermissionOptions(SitePermission.MEDIA_KEY_SYSTEM_ACCESS),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission options is called WHEN autoplay permission is passed as argument THEN available options are return`() {
+ assertEquals(
+ listOf(AutoplayOption.AllowAudioVideo(), AutoplayOption.BlockAudioOnly(), AutoplayOption.BlockAudioVideo()),
+ storage.getSitePermissionOptions(SitePermission.AUTOPLAY),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission label is called WHEN camera permission is passed as argument THEN permission label is return`() {
+ assertEquals(
+ testContext.getString(R.string.preference_phone_feature_camera),
+ storage.getSitePermissionLabel(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission label is called WHEN location permission is passed as argument THEN permission label is return`() {
+ assertEquals(
+ testContext.getString(R.string.preference_phone_feature_location),
+ storage.getSitePermissionLabel(SitePermission.LOCATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission label is called WHEN microphone permission is passed as argument THEN permission label is return`() {
+ assertEquals(
+ testContext.getString(R.string.preference_phone_feature_microphone),
+ storage.getSitePermissionLabel(SitePermission.MICROPHONE),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission label is called WHEN notification permission is passed as argument THEN permission label is return`() {
+ assertEquals(
+ testContext.getString(R.string.preference_phone_feature_notification),
+ storage.getSitePermissionLabel(SitePermission.NOTIFICATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission label is called WHEN media key system access permission is passed as argument THEN permission label is return`() {
+ assertEquals(
+ testContext.getString(R.string.preference_phone_feature_media_key_system_access),
+ storage.getSitePermissionLabel(SitePermission.MEDIA_KEY_SYSTEM_ACCESS),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission label is called WHEN autoplay permission is passed as argument THEN permission label is return`() {
+ assertEquals(
+ testContext.getString(R.string.preference_autoplay),
+ storage.getSitePermissionLabel(SitePermission.AUTOPLAY),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission default option is called WHEN camera permission is passed as argument THEN default option is return`() {
+ assertEquals(
+ SitePermissionOption.AskToAllow(),
+ storage.getSitePermissionDefaultOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission default option is called WHEN location permission is passed as argument THEN default option is return`() {
+ assertEquals(
+ SitePermissionOption.AskToAllow(),
+ storage.getSitePermissionDefaultOption(SitePermission.LOCATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission default option is called WHEN microphone permission is passed as argument THEN default option is return`() {
+ assertEquals(
+ SitePermissionOption.AskToAllow(),
+ storage.getSitePermissionDefaultOption(SitePermission.MICROPHONE),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission default option is called WHEN notification permission is passed as argument THEN default option is return`() {
+ assertEquals(
+ SitePermissionOption.AskToAllow(),
+ storage.getSitePermissionDefaultOption(SitePermission.NOTIFICATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission default option is called WHEN media key system access permission is passed as argument THEN default option is return`() {
+ assertEquals(
+ SitePermissionOption.AskToAllow(),
+ storage.getSitePermissionDefaultOption(SitePermission.MEDIA_KEY_SYSTEM_ACCESS),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission default option is called WHEN autoplay permission is passed as argument THEN default option is return`() {
+ assertEquals(
+ AutoplayOption.BlockAudioOnly(),
+ storage.getSitePermissionDefaultOption(SitePermission.AUTOPLAY),
+ )
+ }
+
+ @Test
+ fun `GIVEN get permission selected option is called WHEN pref_key_allowed is saved in SharedPreferences THEN site permission Allowed is return`() {
+ doReturn(testContext.getString(R.string.pref_key_allowed))
+ .`when`(storage).permissionSelectedOptionByKey(R.string.pref_key_phone_feature_camera)
+
+ assertEquals(
+ SitePermissionOption.Allowed(),
+ storage.permissionSelectedOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get permission selected option is called WHEN pref_key_allow_autoplay_audio_video is saved in SharedPreferences THEN site permission Allow Audio and Video is return`() {
+ doReturn(testContext.getString(R.string.pref_key_allow_autoplay_audio_video))
+ .`when`(storage).permissionSelectedOptionByKey(R.string.pref_key_phone_feature_camera)
+
+ assertEquals(
+ AutoplayOption.AllowAudioVideo(),
+ storage.permissionSelectedOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get permission selected option is called WHEN pref_key_block_autoplay_audio_video is saved in SharedPreferences THEN site permission Block Audio and Video is return`() {
+ doReturn(testContext.getString(R.string.pref_key_block_autoplay_audio_video))
+ .`when`(storage).permissionSelectedOptionByKey(R.string.pref_key_phone_feature_camera)
+
+ assertEquals(
+ AutoplayOption.BlockAudioVideo(),
+ storage.permissionSelectedOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get permission selected option is called WHEN pref_key_blocked is saved in SharedPreferences THEN site permission Blocked is return`() {
+ doReturn(testContext.getString(R.string.pref_key_blocked))
+ .`when`(storage).permissionSelectedOptionByKey(R.string.pref_key_phone_feature_camera)
+
+ assertEquals(
+ SitePermissionOption.Blocked(),
+ storage.permissionSelectedOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get permission selected option is called WHEN pref_key_ask_to_allow is saved in SharedPreferences THEN site permission Ask to allow is return`() {
+ doReturn(testContext.getString(R.string.pref_key_ask_to_allow))
+ .`when`(storage).permissionSelectedOptionByKey(R.string.pref_key_phone_feature_camera)
+
+ assertEquals(
+ SitePermissionOption.AskToAllow(),
+ storage.permissionSelectedOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get permission selected option is called WHEN pref_key_block_autoplay_audio_only is saved in SharedPreferences THEN site permission Block Audio only is return`() {
+ doReturn(testContext.getString(R.string.pref_key_block_autoplay_audio_only))
+ .`when`(storage).permissionSelectedOptionByKey(R.string.pref_key_phone_feature_camera)
+
+ assertEquals(
+ AutoplayOption.BlockAudioOnly(),
+ storage.permissionSelectedOption(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission camera is passed as argument THEN pref_key_phone_feature_camera is return`() {
+ assertEquals(
+ R.string.pref_key_phone_feature_camera,
+ storage.getSitePermissionPreferenceId(SitePermission.CAMERA),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission location is passed as argument THEN pref_key_phone_feature_location is return`() {
+ assertEquals(
+ R.string.pref_key_phone_feature_location,
+ storage.getSitePermissionPreferenceId(SitePermission.LOCATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission microphone is passed as argument THEN pref_key_phone_feature_microphone is return`() {
+ assertEquals(
+ R.string.pref_key_phone_feature_microphone,
+ storage.getSitePermissionPreferenceId(SitePermission.MICROPHONE),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission notification is passed as argument THEN pref_key_phone_feature_notification is return`() {
+ assertEquals(
+ R.string.pref_key_phone_feature_notification,
+ storage.getSitePermissionPreferenceId(SitePermission.NOTIFICATION),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission autoplay is passed as argument THEN pref_key_autoplay is return`() {
+ assertEquals(
+ R.string.pref_key_autoplay,
+ storage.getSitePermissionPreferenceId(SitePermission.AUTOPLAY),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission autoplay audible is passed as argument THEN pref_key_allow_autoplay_audio_video is return`() {
+ assertEquals(
+ R.string.pref_key_allow_autoplay_audio_video,
+ storage.getSitePermissionPreferenceId(SitePermission.AUTOPLAY_AUDIBLE),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission autoplay inaudible is passed as argument THEN pref_key_block_autoplay_audio_video is return`() {
+ assertEquals(
+ R.string.pref_key_block_autoplay_audio_video,
+ storage.getSitePermissionPreferenceId(SitePermission.AUTOPLAY_INAUDIBLE),
+ )
+ }
+
+ @Test
+ fun `GIVEN get site permission preference id is called WHEN site permission media key system access is passed as argument THEN pref_key_browser_feature_media_key_system_access is return`() {
+ assertEquals(
+ R.string.pref_key_browser_feature_media_key_system_access,
+ storage.getSitePermissionPreferenceId(SitePermission.MEDIA_KEY_SYSTEM_ACCESS),
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStoreTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStoreTest.kt
new file mode 100644
index 0000000000..a76ecb95d6
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionOptionsStoreTest.kt
@@ -0,0 +1,89 @@
+/* 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.focus.sitepermissions
+
+import mozilla.components.support.test.ext.joinBlocking
+import mozilla.components.support.test.mock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.verify
+import org.mozilla.focus.settings.permissions.AutoplayOption
+import org.mozilla.focus.settings.permissions.SitePermissionOption
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsScreenAction
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsScreenState
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsScreenStore
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsStorage
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsStorageMiddleware
+
+class SitePermissionOptionsStoreTest {
+ private lateinit var sitePermissionOptionsStorageMiddleware: SitePermissionOptionsStorageMiddleware
+ private lateinit var store: SitePermissionOptionsScreenStore
+ private lateinit var sitePermissionState: SitePermissionOptionsScreenState
+ private val storage: SitePermissionOptionsStorage = mock()
+
+ @Before
+ fun setup() {
+ sitePermissionOptionsStorageMiddleware = SitePermissionOptionsStorageMiddleware(SitePermission.CAMERA, storage)
+ sitePermissionState = SitePermissionOptionsScreenState()
+
+ doReturn(listOf(SitePermissionOption.AskToAllow(), SitePermissionOption.Blocked())).`when`(storage).getSitePermissionOptions(SitePermission.CAMERA)
+ doReturn(SitePermissionOption.AskToAllow()).`when`(storage).permissionSelectedOption(SitePermission.CAMERA)
+ doReturn("Camera").`when`(storage).getSitePermissionLabel(SitePermission.CAMERA)
+ doReturn(false).`when`(storage).isAndroidPermissionGranted(SitePermission.CAMERA)
+
+ store = SitePermissionOptionsScreenStore(sitePermissionState, listOf(sitePermissionOptionsStorageMiddleware))
+ }
+
+ @Test
+ fun `GIVEN site permission screen store WHEN android permission action is dispatched THEN site permission options screen state is updated`() {
+ store.dispatch(SitePermissionOptionsScreenAction.AndroidPermission(true)).joinBlocking()
+
+ verify(storage).getSitePermissionOptions(SitePermission.CAMERA)
+ verify(storage).permissionSelectedOption(SitePermission.CAMERA)
+ verify(storage).getSitePermissionLabel(SitePermission.CAMERA)
+ verify(storage).isAndroidPermissionGranted(SitePermission.CAMERA)
+ assertTrue(store.state.isAndroidPermissionGranted)
+ }
+
+ @Test
+ fun `GIVEN site permission screen store WHEN select permission action is dispatched THEN site permission options screen state is updated`() {
+ store.dispatch(SitePermissionOptionsScreenAction.Select(SitePermissionOption.Blocked())).joinBlocking()
+
+ verify(storage).saveCurrentSitePermissionOptionInSharePref(SitePermissionOption.Blocked(), SitePermission.CAMERA)
+ assertEquals(SitePermissionOption.Blocked(), store.state.selectedSitePermissionOption)
+ }
+
+ @Test
+ fun `GIVEN site permission screen store WHEN update site permission options is dispatched THEN site permission options screen state is updated`() {
+ val sitePermissionLabel = "Autoplay"
+ store.dispatch(
+ SitePermissionOptionsScreenAction.UpdateSitePermissionOptions(
+ listOf(
+ AutoplayOption.BlockAudioOnly(),
+ AutoplayOption.AllowAudioVideo(),
+ AutoplayOption.BlockAudioVideo(),
+ ),
+ AutoplayOption.AllowAudioVideo(),
+ sitePermissionLabel,
+ true,
+ ),
+ ).joinBlocking()
+
+ assertEquals(AutoplayOption.AllowAudioVideo(), store.state.selectedSitePermissionOption)
+ assertTrue(store.state.isAndroidPermissionGranted)
+ assertEquals(
+ listOf(
+ AutoplayOption.BlockAudioOnly(),
+ AutoplayOption.AllowAudioVideo(),
+ AutoplayOption.BlockAudioVideo(),
+ ),
+ store.state.sitePermissionOptionList,
+ )
+ assertEquals(sitePermissionLabel, store.state.sitePermissionLabel)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionsFragmentTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionsFragmentTest.kt
new file mode 100644
index 0000000000..0072c55dee
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/sitepermissions/SitePermissionsFragmentTest.kt
@@ -0,0 +1,57 @@
+/* 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.focus.sitepermissions
+
+import androidx.preference.Preference
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mozilla.focus.R
+import org.mozilla.focus.settings.permissions.SitePermissionsFragment
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermission
+import org.mozilla.focus.settings.permissions.permissionoptions.SitePermissionOptionsStorage
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SitePermissionsFragmentTest {
+
+ private val storage: SitePermissionOptionsStorage = mock()
+ private lateinit var fragment: SitePermissionsFragment
+
+ @Before
+ fun setup() {
+ fragment = spy(SitePermissionsFragment())
+ doReturn(testContext).`when`(fragment).context
+ fragment.storage = storage
+ }
+
+ @Test
+ fun `GIVEN site permission fragment WHEN fragment is created THEN summary for Preference is set from storage`() {
+ val cameraPreference = Preference(testContext)
+ doReturn(cameraPreference).`when`(fragment).getPreference(R.string.pref_key_phone_feature_camera)
+ val expectedSummary = testContext.getString(R.string.phone_feature_blocked_by_android)
+ doReturn(expectedSummary).`when`(storage).getSitePermissionOptionSelectedLabel(SitePermission.CAMERA)
+
+ fragment.initPhoneFeature(SitePermission.CAMERA)
+
+ assertEquals(expectedSummary, cameraPreference.summary)
+ }
+
+ @Test
+ fun `GIVEN site permission fragment WHEN preference is clicked THEN navigate to site permission options screen it called`() {
+ val cameraPreference = Preference(testContext)
+ doReturn(cameraPreference).`when`(fragment).getPreference(R.string.pref_key_phone_feature_camera)
+
+ fragment.initPhoneFeature(SitePermission.CAMERA)
+ cameraPreference.performClick()
+
+ verify(fragment).navigateToSitePermissionOptionsScreen(SitePermission.CAMERA)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/GleanMetricsServiceTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/GleanMetricsServiceTest.kt
new file mode 100644
index 0000000000..28166ce263
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/GleanMetricsServiceTest.kt
@@ -0,0 +1,42 @@
+/* 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.focus.telemetry
+
+import android.content.Context
+import kotlinx.coroutines.runBlocking
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.engine.Engine
+import mozilla.components.feature.search.telemetry.SearchProviderModel
+import mozilla.components.feature.search.telemetry.ads.AdsTelemetry
+import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry
+import org.junit.Test
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mozilla.focus.Components
+
+class GleanMetricsServiceTest {
+ @Test
+ fun `WHEN installSearchTelemetryExtensions is called THEN install the ads and search telemetry extensions`() {
+ val components = mock(Components::class.java)
+ val store = mock(BrowserStore::class.java)
+ val engine = mock(Engine::class.java)
+ val adsExtension = mock(AdsTelemetry::class.java)
+ val searchExtension = mock(InContentTelemetry::class.java)
+ val providerList: List = mock()
+ doReturn(engine).`when`(components).engine
+ doReturn(store).`when`(components).store
+ doReturn(adsExtension).`when`(components).adsTelemetry
+ doReturn(searchExtension).`when`(components).searchTelemetry
+ val glean = GleanMetricsService(mock(Context::class.java))
+
+ runBlocking {
+ glean.installSearchTelemetryExtensions(components, providerList)
+
+ verify(adsExtension).install(engine, store, providerList)
+ verify(searchExtension).install(engine, store, providerList)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessorTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessorTest.kt
new file mode 100644
index 0000000000..d95da8aac5
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/ProfilerMarkerFactProcessorTest.kt
@@ -0,0 +1,96 @@
+/* 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.focus.telemetry
+
+import android.os.Handler
+import android.os.Looper
+import mozilla.components.concept.base.profiler.Profiler
+import mozilla.components.support.base.Component
+import mozilla.components.support.base.facts.Action
+import mozilla.components.support.base.facts.Fact
+import mozilla.components.support.test.any
+import mozilla.components.support.test.argumentCaptor
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoInteractions
+import org.mockito.Mockito.`when`
+
+class ProfilerMarkerFactProcessorTest {
+
+ private val profiler: Profiler = mock(Profiler::class.java)
+ private val mainHandler: Handler = mock(Handler::class.java)
+ lateinit var processor: ProfilerMarkerFactProcessor
+
+ var myLooper: Looper? = null
+
+ @Before
+ fun setUp() {
+ myLooper = null
+ processor = ProfilerMarkerFactProcessor({ profiler }, mainHandler, { myLooper })
+ }
+
+ @Test
+ fun `Test on the main thread`() {
+ // GIVEN we are on the main thread
+ myLooper = mainHandler.looper // main thread
+
+ // WHEN a fact with an implementation detail action is received
+ val fact = newFact(Action.IMPLEMENTATION_DETAIL)
+ processor.process(fact)
+
+ // THEN a profiler marker is added now
+ verify(profiler).addMarker(fact.item)
+ }
+
+ @Test
+ fun `Test not on the main thread`() {
+ // GIVEN we are not on the main thread
+ myLooper = mock(Looper::class.java) // off main thread
+
+ // WHEN a fact with an implementation detail action is received
+ val mainThreadPostedArg = argumentCaptor()
+ `when`(profiler.getProfilerTime()).thenReturn(100.0)
+
+ val fact = newFact(Action.IMPLEMENTATION_DETAIL)
+ processor.process(fact)
+
+ // THEN adding the marker is posted to the main thread
+ verify(mainHandler).post(mainThreadPostedArg.capture())
+ verifyProfilerAddMarkerWasNotCalled()
+
+ mainThreadPostedArg.value.run()
+ verify(profiler).addMarker(fact.item, 100.0, 100.0, null)
+ }
+
+ @Test
+ fun `Test non-implementation detail`() {
+ // WHEN a fact with a non-implementation detail action is received
+ val fact = newFact(Action.CANCEL)
+ processor.process(fact)
+
+ // THEN no profiler marker is added
+ verifyNoInteractions(profiler)
+ }
+
+ private fun verifyProfilerAddMarkerWasNotCalled() {
+ verify(profiler, never()).addMarker(any())
+ verify(profiler, never()).addMarker(any(), any() as Double?)
+ verify(profiler, never()).addMarker(any(), any() as String?)
+ verify(profiler, never()).addMarker(any(), any(), any())
+ verify(profiler, never()).addMarker(any(), any(), any(), any())
+ }
+}
+
+private fun newFact(
+ action: Action,
+ item: String = "itemName",
+) = Fact(
+ Component.BROWSER_SESSION_STORAGE,
+ action,
+ item,
+)
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupActivityLogTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupActivityLogTest.kt
new file mode 100644
index 0000000000..b08ebb9f6e
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupActivityLogTest.kt
@@ -0,0 +1,99 @@
+/* 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.focus.telemetry
+
+import android.app.Activity
+import android.app.Application
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import mozilla.components.support.test.any
+import mozilla.components.support.test.mock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog
+import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog.LogEntry
+
+class StartupActivityLogTest {
+
+ private lateinit var log: StartupActivityLog
+ private lateinit var appObserver: StartupActivityLog.StartupLogAppLifecycleObserver
+ private lateinit var activityCallbacks: StartupActivityLog.StartupLogActivityLifecycleCallbacks
+
+ @Before
+ fun setUp() {
+ log = StartupActivityLog()
+ val (appObserver, activityCallbacks) = log.getObserversForTesting()
+ this.appObserver = appObserver
+ this.activityCallbacks = activityCallbacks
+ }
+
+ @Test
+ fun `WHEN register is called THEN it is registered`() {
+ val app: Application = mock()
+ val lifecycleOwner: LifecycleOwner = mock()
+ val mLifecycle: Lifecycle = mock()
+ `when`(lifecycleOwner.lifecycle).thenReturn(mLifecycle)
+
+ log.registerInAppOnCreate(app, lifecycleOwner)
+
+ verify(app).registerActivityLifecycleCallbacks(any())
+ }
+
+ @Test // we test start and stop individually due to the clear-on-stop behavior.
+ fun `WHEN app observer start is called THEN it is added directly to the log`() {
+ assertTrue(log.log.isEmpty())
+
+ appObserver.onStart(mock())
+ assertEquals(listOf(LogEntry.AppStarted), log.log)
+
+ appObserver.onStart(mock())
+ assertEquals(listOf(LogEntry.AppStarted, LogEntry.AppStarted), log.log)
+ }
+
+ @Test // we test start and stop individually due to the clear-on-stop behavior.
+ fun `WHEN app observer stop is called THEN it is added directly to the log`() {
+ assertTrue(log.log.isEmpty())
+
+ appObserver.onStop(mock())
+ assertEquals(listOf(LogEntry.AppStopped), log.log)
+ }
+
+ @Test
+ fun `WHEN activity callback methods are called THEN they are added directly to the log`() {
+ assertTrue(log.log.isEmpty())
+ val expected = mutableListOf()
+
+ val activityClass = mock()::class.java // mockk can't mock Class<...>
+
+ activityCallbacks.onActivityCreated(mock(), null)
+ expected.add(LogEntry.ActivityCreated(activityClass))
+ assertEquals(expected, log.log)
+
+ activityCallbacks.onActivityStarted(mock())
+ expected.add(LogEntry.ActivityStarted(activityClass))
+ assertEquals(expected, log.log)
+
+ activityCallbacks.onActivityStopped(mock())
+ expected.add(LogEntry.ActivityStopped(activityClass))
+ assertEquals(expected, log.log)
+ }
+
+ @Test
+ fun `WHEN app STOPPED is called THEN the log is emptied expect for the stop event`() {
+ assertTrue(log.log.isEmpty())
+
+ activityCallbacks.onActivityCreated(mock(), null)
+ activityCallbacks.onActivityStarted(mock())
+ appObserver.onStart(mock())
+ assertEquals(3, log.log.size)
+
+ appObserver.onStop(mock())
+ assertEquals(listOf(LogEntry.AppStopped), log.log)
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupPathProviderTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupPathProviderTest.kt
new file mode 100644
index 0000000000..cc023ee079
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupPathProviderTest.kt
@@ -0,0 +1,213 @@
+/* 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.focus.telemetry
+
+import android.content.Intent
+import androidx.lifecycle.Lifecycle
+import mozilla.components.support.test.any
+import mozilla.components.support.test.mock
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider.StartupPath
+
+class StartupPathProviderTest {
+
+ private lateinit var provider: StartupPathProvider
+ private lateinit var callbacks: StartupPathProvider.StartupPathLifecycleObserver
+
+ private val intent: Intent = mock()
+
+ @Before
+ fun setUp() {
+ provider = StartupPathProvider()
+ callbacks = provider.getTestCallbacks()
+ }
+
+ @Test
+ fun `WHEN attach is called THEN the provider is registered to the lifecycle`() {
+ val lifecycle = mock()
+ provider.attachOnActivityOnCreate(lifecycle, null)
+
+ verify(lifecycle).addObserver(any())
+ }
+
+ @Test
+ fun `WHEN calling attach THEN the intent is passed to on intent received`() {
+ // With this test, we're basically saying, "attach..." does the same thing as
+ // "onIntentReceived" so we don't need to duplicate all the tests we run for
+ // "onIntentReceived".
+ val spyProvider = spy(provider)
+ spyProvider.attachOnActivityOnCreate(mock(), intent)
+
+ verify(spyProvider).onIntentReceived(intent)
+ }
+
+ @Test
+ fun `GIVEN no intent is received and the activity is not started WHEN getting the start up path THEN it is not set`() {
+ assertEquals(StartupPath.NOT_SET, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN a main intent is received but the activity is not started yet WHEN getting the start up path THEN main is returned`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+ provider.onIntentReceived(intent)
+ assertEquals(StartupPath.MAIN, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN a main intent is received and the app is started WHEN getting the start up path THEN it is main`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+ callbacks.onCreate(mock())
+ provider.onIntentReceived(intent)
+ callbacks.onStart(mock())
+
+ assertEquals(StartupPath.MAIN, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched from the homeScreen WHEN getting the start up path THEN it is main`() {
+ // There's technically more to a homeScreen Intent but it's fine for now.
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+ launchApp(intent)
+ assertEquals(StartupPath.MAIN, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched by app link WHEN getting the start up path THEN it is view`() {
+ // There's technically more to a homeScreen Intent but it's fine for now.
+ doReturn(Intent.ACTION_VIEW).`when`(intent).action
+
+ launchApp(intent)
+
+ assertEquals(StartupPath.VIEW, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched by a send action WHEN getting the start up path THEN it is unknown`() {
+ doReturn(Intent.ACTION_SEND).`when`(intent).action
+
+ launchApp(intent)
+
+ assertEquals(StartupPath.UNKNOWN, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched by a null intent (is this possible) WHEN getting the start up path THEN it is not set`() {
+ callbacks.onCreate(mock())
+ provider.onIntentReceived(null)
+ callbacks.onStart(mock())
+ callbacks.onResume(mock())
+
+ assertEquals(StartupPath.NOT_SET, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched to the homeScreen and stopped WHEN getting the start up path THEN it is not set`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+
+ launchApp(intent)
+ stopLaunchedApp()
+
+ assertEquals(StartupPath.NOT_SET, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched to the homeScreen, stopped, and relaunched warm from app link WHEN getting the start up path THEN it is view`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+
+ launchApp(intent)
+ stopLaunchedApp()
+
+ doReturn(Intent.ACTION_VIEW).`when`(intent).action
+
+ startStoppedApp(intent)
+
+ assertEquals(StartupPath.VIEW, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched to the homeScreen, stopped, and relaunched warm from the app switcher WHEN getting the start up path THEN it is not set`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+
+ launchApp(intent)
+ stopLaunchedApp()
+ startStoppedAppFromAppSwitcher()
+
+ assertEquals(StartupPath.NOT_SET, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched to the homeScreen, paused, and resumed WHEN getting the start up path THEN it returns the initial intent value`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+
+ launchApp(intent)
+ callbacks.onPause(mock())
+ callbacks.onResume(mock())
+
+ assertEquals(StartupPath.MAIN, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched with an intent and receives an intent while the activity is foregrounded WHEN getting the start up path THEN it returns the initial intent value`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+
+ launchApp(intent)
+ doReturn(Intent.ACTION_VIEW).`when`(intent).action
+
+ receiveIntentInForeground(intent)
+
+ assertEquals(StartupPath.MAIN, provider.startupPathForActivity)
+ }
+
+ @Test
+ fun `GIVEN the app is launched, stopped, started from the app switcher and receives an intent in the foreground WHEN getting the start up path THEN it returns not set`() {
+ doReturn(Intent.ACTION_MAIN).`when`(intent).action
+
+ launchApp(intent)
+ stopLaunchedApp()
+ startStoppedAppFromAppSwitcher()
+ doReturn(Intent.ACTION_VIEW).`when`(intent).action
+
+ receiveIntentInForeground(intent)
+
+ assertEquals(StartupPath.NOT_SET, provider.startupPathForActivity)
+ }
+
+ private fun launchApp(intent: Intent) {
+ callbacks.onCreate(mock())
+ provider.onIntentReceived(intent)
+ callbacks.onStart(mock())
+ callbacks.onResume(mock())
+ }
+
+ private fun stopLaunchedApp() {
+ callbacks.onPause(mock())
+ callbacks.onStop(mock())
+ }
+
+ private fun startStoppedApp(intent: Intent) {
+ callbacks.onStart(mock())
+ provider.onIntentReceived(intent)
+ callbacks.onResume(mock())
+ }
+
+ private fun startStoppedAppFromAppSwitcher() {
+ // What makes the app switcher case special is it starts the app without an intent.
+ callbacks.onStart(mock())
+ callbacks.onResume(mock())
+ }
+
+ private fun receiveIntentInForeground(intent: Intent) {
+ // To my surprise, the app is paused before receiving an intent on Pixel 2.
+ callbacks.onPause(mock())
+ provider.onIntentReceived(intent)
+ callbacks.onResume(mock())
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupStateProviderTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupStateProviderTest.kt
new file mode 100644
index 0000000000..db8d452b63
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupStateProviderTest.kt
@@ -0,0 +1,417 @@
+/* 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.focus.telemetry
+
+import mozilla.components.support.test.mock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+import org.mozilla.focus.activity.IntentReceiverActivity
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider
+import org.mozilla.focus.telemetry.startuptelemetry.AppStartReasonProvider.StartReason
+import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog
+import org.mozilla.focus.telemetry.startuptelemetry.StartupActivityLog.LogEntry
+import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider.StartupState
+
+class StartupStateProviderTest {
+
+ private lateinit var provider: StartupStateProvider
+ private var startupActivityLog: StartupActivityLog = mock()
+ private var startReasonProvider: AppStartReasonProvider = mock()
+
+ private lateinit var logEntries: MutableList
+
+ private val mainActivityClass = MainActivity::class.java
+ private val irActivityClass = IntentReceiverActivity::class.java
+
+ @Before
+ fun setUp() {
+ provider = StartupStateProvider(startupActivityLog, startReasonProvider)
+
+ logEntries = mutableListOf()
+ Mockito.doReturn(logEntries).`when`(startupActivityLog).log
+ Mockito.doReturn(logEntries).`when`(startupActivityLog).log
+ Mockito.doReturn(StartReason.ACTIVITY).`when`(startReasonProvider).reason
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is cold start THEN cold start is true`() {
+ forEachColdStartEntries { index ->
+ assertTrue("$index", provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN warm start THEN cold start is false`() {
+ forEachWarmStartEntries { index ->
+ assertFalse("$index", provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN hot start THEN cold start is false`() {
+ forEachHotStartEntries { index ->
+ assertFalse("$index", provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is cold start THEN warm start is false`() {
+ forEachColdStartEntries { index ->
+ assertFalse("$index", provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is warm start THEN warm start is true`() {
+ forEachWarmStartEntries { index ->
+ assertTrue("$index", provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is hot start THEN warm start is false`() {
+ forEachHotStartEntries { index ->
+ assertFalse("$index", provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is cold start THEN hot start is false`() {
+ forEachColdStartEntries { index ->
+ assertFalse("$index", provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is warm start THEN hot start is false`() {
+ forEachWarmStartEntries { index ->
+ assertFalse("$index", provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN is hot start THEN hot start is true`() {
+ forEachHotStartEntries { index ->
+ assertTrue("$index", provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN we launched HA through a drawing IntentRA THEN start up is not cold`() {
+ // These entries mimic observed behavior for local code changes.
+ logEntries.addAll(
+ listOf(
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityStarted(irActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.ActivityStopped(irActivityClass),
+ ),
+ )
+ assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN we launched HA through a drawing IntentRA THEN start up is not warm`() {
+ // These entries mimic observed behavior for local code changes.
+ logEntries.addAll(
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityStarted(irActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.ActivityStopped(irActivityClass),
+ ),
+ )
+ assertFalse(provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN we launched HA through a drawing IntentRA THEN start up is not hot`() {
+ // These entries mimic observed behavior for local code changes.
+ logEntries.addAll(
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityStarted(irActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.ActivityStopped(irActivityClass),
+ ),
+ )
+ assertFalse(provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN two MainActivities are created THEN start up is not cold`() {
+ // We're making an assumption about how this would work based on previous observed patterns.
+ // AIUI, we should never have more than one MainActivity.
+ logEntries.addAll(
+ listOf(
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.ActivityStopped(mainActivityClass),
+ ),
+ )
+ assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN an activity hasn't been created yet THEN start up is not cold`() {
+ assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN an activity hasn't started yet THEN start up is not cold`() {
+ logEntries.addAll(
+ listOf(
+ LogEntry.ActivityCreated(mainActivityClass),
+ ),
+ )
+ assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app did not start for an activity WHEN is cold is checked THEN it returns false`() {
+ Mockito.doReturn(StartReason.NON_ACTIVITY).`when`(startReasonProvider).reason
+ assertFalse(provider.isColdStartForStartedActivity(mainActivityClass))
+
+ forEachColdStartEntries { index ->
+ assertFalse("$index", provider.isColdStartForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `GIVEN the app has not been stopped WHEN an activity has not been created THEN it's not a warm start`() {
+ assertFalse(provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app has been stopped WHEN an activity has not been created THEN it's not a warm start`() {
+ logEntries.add(LogEntry.AppStopped)
+ assertFalse(provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app has been stopped WHEN an activity has not been started THEN it's not a warm start`() {
+ logEntries.addAll(
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityCreated(mainActivityClass),
+ ),
+ )
+ assertFalse(provider.isWarmStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app has not been stopped WHEN an activity has not been created THEN it's not a hot start`() {
+ assertFalse(provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app has been stopped WHEN an activity has not been created THEN it's not a hot start`() {
+ logEntries.add(LogEntry.AppStopped)
+ assertFalse(provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app has been stopped WHEN an activity has not been started THEN it's not a hot start`() {
+ logEntries.addAll(
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityCreated(mainActivityClass),
+ ),
+ )
+ assertFalse(provider.isHotStartForStartedActivity(mainActivityClass))
+ }
+
+ @Test
+ fun `GIVEN the app started for an activity WHEN it is a cold start THEN get startup state is cold`() {
+ forEachColdStartEntries { index ->
+ assertEquals("$index", StartupState.COLD, provider.getStartupStateForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `WHEN it is a warm start THEN get startup state is warm`() {
+ forEachWarmStartEntries { index ->
+ assertEquals("$index", StartupState.WARM, provider.getStartupStateForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `WHEN it is a hot start THEN get startup state is hot`() {
+ forEachHotStartEntries { index ->
+ assertEquals("$index", StartupState.HOT, provider.getStartupStateForStartedActivity(mainActivityClass))
+ }
+ }
+
+ @Test
+ fun `WHEN two activities are started THEN get startup state is unknown`() {
+ logEntries.addAll(
+ listOf(
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityStarted(irActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.ActivityStopped(irActivityClass),
+ ),
+ )
+
+ assertEquals(StartupState.UNKNOWN, provider.getStartupStateForStartedActivity(mainActivityClass))
+ }
+
+ private fun forEachColdStartEntries(block: (index: Int) -> Unit) {
+ // These entries mimic observed behavior.
+ //
+ // MAIN: open HomeActivity directly.
+ val coldStartEntries = listOf(
+ listOf(
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // VIEW: open non-drawing IntentReceiverActivity, then HomeActivity.
+ ),
+ listOf(
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ ),
+ )
+
+ forEachStartEntry(coldStartEntries, block)
+ }
+
+ private fun forEachWarmStartEntries(block: (index: Int) -> Unit) {
+ // These entries mimic observed behavior. We test both truncated (i.e. the current behavior
+ // with the optimization to prevent an infinite log) and untruncated (the behavior without
+ // such an optimization).
+ //
+ // truncated MAIN: open HomeActivity directly.
+ val warmStartEntries = listOf(
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // untruncated MAIN: open MainActivity directly.
+ ),
+ listOf(
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // truncated VIEW: open non-drawing IntentReceiverActivity, then MainActivity.
+ ),
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // untruncated VIEW: open non-drawing IntentReceiverActivity, then MainActivity.
+ ),
+ listOf(
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ ),
+ )
+
+ forEachStartEntry(warmStartEntries, block)
+ }
+
+ private fun forEachHotStartEntries(block: (index: Int) -> Unit) {
+ // These entries mimic observed behavior. We test both truncated (i.e. the current behavior
+ // with the optimization to prevent an infinite log) and untruncated (the behavior without
+ // such an optimization).
+ //
+ // truncated MAIN: open HomeActivity directly.
+ val hotStartEntries = listOf(
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // untruncated MAIN: open HomeActivity directly.
+ ),
+ listOf(
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // truncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity.
+ ),
+ listOf(
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+
+ // untruncated VIEW: open non-drawing IntentReceiverActivity, then HomeActivity.
+ ),
+ listOf(
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityCreated(mainActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ LogEntry.AppStopped,
+ LogEntry.ActivityStopped(mainActivityClass),
+ LogEntry.ActivityCreated(irActivityClass),
+ LogEntry.ActivityStarted(mainActivityClass),
+ LogEntry.AppStarted,
+ ),
+ )
+
+ forEachStartEntry(hotStartEntries, block)
+ }
+
+ private fun forEachStartEntry(entries: List>, block: (index: Int) -> Unit) {
+ entries.forEachIndexed { index, startEntry ->
+ logEntries.clear()
+ logEntries.addAll(startEntry)
+ block(index)
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupTypeTelemetryTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupTypeTelemetryTest.kt
new file mode 100644
index 0000000000..2d37068eda
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/telemetry/StartupTypeTelemetryTest.kt
@@ -0,0 +1,152 @@
+/* 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.focus.telemetry
+
+import androidx.lifecycle.Lifecycle
+import kotlinx.coroutines.test.advanceUntilIdle
+import mozilla.components.support.ktx.kotlin.crossProduct
+import mozilla.components.support.test.any
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.test.rule.MainCoroutineRule
+import mozilla.components.support.test.rule.runTestOnMain
+import mozilla.telemetry.glean.testing.GleanTestRule
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mozilla.focus.GleanMetrics.PerfStartup
+import org.mozilla.focus.activity.MainActivity
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider.StartupPath
+import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider
+import org.mozilla.focus.telemetry.startuptelemetry.StartupStateProvider.StartupState
+import org.mozilla.focus.telemetry.startuptelemetry.StartupTypeTelemetry
+import org.mozilla.focus.telemetry.startuptelemetry.StartupTypeTelemetry.StartupTypeLifecycleObserver
+import org.robolectric.RobolectricTestRunner
+
+private val validTelemetryLabels = run {
+ val allStates = listOf("cold", "warm", "hot", "unknown")
+ val allPaths = listOf("main", "view", "unknown")
+
+ allStates.crossProduct(allPaths) { state, path -> "${state}_$path" }.toSet()
+}
+
+private val activityClass = MainActivity::class.java
+
+@RunWith(RobolectricTestRunner::class)
+class StartupTypeTelemetryTest {
+
+ private lateinit var telemetry: StartupTypeTelemetry
+ private lateinit var callbacks: StartupTypeLifecycleObserver
+ private var stateProvider: StartupStateProvider = mock()
+ private var pathProvider: StartupPathProvider = mock()
+
+ @get:Rule
+ val coroutinesTestRule = MainCoroutineRule()
+
+ @get:Rule
+ val gleanTestRule = GleanTestRule(testContext)
+
+ @Before
+ fun setUp() {
+ telemetry = spy(StartupTypeTelemetry(stateProvider, pathProvider))
+ callbacks = telemetry.getTestCallbacks()
+ }
+
+ @Test
+ fun `WHEN attach is called THEN it is registered to the lifecycle`() {
+ val lifecycle = mock()
+
+ telemetry.attachOnMainActivityOnCreate(lifecycle)
+
+ verify(lifecycle).addObserver(any())
+ }
+
+ @Test
+ fun `GIVEN all possible path and state combinations WHEN record telemetry THEN the labels are incremented the appropriate number of times`() = runTestOnMain {
+ val allPossibleInputArgs = StartupState.values().toList().crossProduct(
+ StartupPath.values().toList(),
+ ) { state, path ->
+ Pair(state, path)
+ }
+
+ allPossibleInputArgs.forEach { (state, path) ->
+ doReturn(state).`when`(stateProvider).getStartupStateForStartedActivity(activityClass)
+ doReturn(path).`when`(pathProvider).startupPathForActivity
+
+ telemetry.record(coroutinesTestRule.testDispatcher)
+ advanceUntilIdle()
+ }
+
+ validTelemetryLabels.forEach { label ->
+ // Path == NOT_SET gets bucketed with Path == UNKNOWN so we'll increment twice for those.
+ val expected = if (label.endsWith("unknown")) 2 else 1
+ assertEquals("label: $label", expected, PerfStartup.startupType[label].testGetValue())
+ }
+
+ // All invalid labels go to a single bucket: let's verify it has no value.
+ assertNull(PerfStartup.startupType["__other__"].testGetValue())
+ }
+
+ @Test
+ fun `WHEN record is called THEN telemetry is recorded with the appropriate label`() = runTestOnMain {
+ doReturn(StartupState.COLD).`when`(stateProvider).getStartupStateForStartedActivity(activityClass)
+ doReturn(StartupPath.MAIN).`when`(pathProvider).startupPathForActivity
+
+ telemetry.record(coroutinesTestRule.testDispatcher)
+ advanceUntilIdle()
+
+ assertEquals(1, PerfStartup.startupType["cold_main"].testGetValue())
+ }
+
+ @Test
+ fun `GIVEN the activity is launched WHEN onResume is called THEN we record the telemetry`() {
+ launchApp()
+ verify(telemetry).record(any())
+ }
+
+ @Test
+ fun `GIVEN the activity is launched WHEN the activity is paused and resumed THEN record is not called`() {
+ // This part of the test duplicates another test but it's needed to initialize the state of this test.
+ launchApp()
+ verify(telemetry).record(any())
+
+ callbacks.onPause(mock())
+ callbacks.onResume(mock())
+
+ verify(telemetry).record(any()) // i.e. this shouldn't be called again.
+ }
+
+ @Test
+ fun `GIVEN the activity is launched WHEN the activity is stopped and resumed THEN record is called again`() {
+ // This part of the test duplicates another test but it's needed to initialize the state of this test.
+ launchApp()
+ verify(telemetry).record(any())
+
+ callbacks.onPause(mock())
+ callbacks.onStop(mock())
+ callbacks.onStart(mock())
+ callbacks.onResume(mock())
+
+ verify(telemetry, times(2)).record(any())
+ }
+
+ private fun launchApp() {
+ // What these return isn't important.
+ doReturn(StartupState.COLD).`when`(stateProvider).getStartupStateForStartedActivity(activityClass)
+ doReturn(StartupPath.MAIN).`when`(pathProvider).startupPathForActivity
+
+ callbacks.onCreate(mock())
+ callbacks.onStart(mock())
+ callbacks.onResume(mock())
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/topsites/DefaultTopSitesStorageTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/topsites/DefaultTopSitesStorageTest.kt
new file mode 100644
index 0000000000..27ff6a5378
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/topsites/DefaultTopSitesStorageTest.kt
@@ -0,0 +1,136 @@
+/* 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.focus.topsites
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import mozilla.components.feature.top.sites.PinnedSiteStorage
+import mozilla.components.feature.top.sites.TopSite
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@ExperimentalCoroutinesApi
+class DefaultTopSitesStorageTest {
+
+ private val pinnedSitesStorage: PinnedSiteStorage = mock()
+
+ @Test
+ fun `WHEN a top site is added THEN the pinned sites storage is called`() = runTest(UnconfinedTestDispatcher()) {
+ val defaultTopSitesStorage = DefaultTopSitesStorage(
+ pinnedSitesStorage,
+ coroutineContext,
+ )
+
+ defaultTopSitesStorage.addTopSite("Mozilla", "https://mozilla.com", isDefault = false)
+
+ verify(pinnedSitesStorage).addPinnedSite(
+ "Mozilla",
+ "https://mozilla.com",
+ isDefault = false,
+ )
+ }
+
+ @Test
+ fun `WHEN a top site is removed THEN the pinned sites storage is called`() = runTest(UnconfinedTestDispatcher()) {
+ val defaultTopSitesStorage = DefaultTopSitesStorage(
+ pinnedSitesStorage,
+ coroutineContext,
+ )
+
+ val pinnedSite = TopSite.Pinned(
+ id = 2,
+ title = "Firefox",
+ url = "https://firefox.com",
+ createdAt = 2,
+ )
+
+ defaultTopSitesStorage.removeTopSite(pinnedSite)
+
+ verify(pinnedSitesStorage).removePinnedSite(pinnedSite)
+ }
+
+ @Test
+ fun `WHEN a top site is updated THEN the pinned sites storage is called`() = runTest(UnconfinedTestDispatcher()) {
+ val defaultTopSitesStorage = DefaultTopSitesStorage(
+ pinnedSitesStorage,
+ coroutineContext,
+ )
+
+ val pinnedSite = TopSite.Pinned(
+ id = 2,
+ title = "Wikipedia",
+ url = "https://wikipedia.com",
+ createdAt = 2,
+ )
+ defaultTopSitesStorage.updateTopSite(
+ pinnedSite,
+ "Wiki",
+ "https://en.wikipedia.org/wiki/Wiki",
+ )
+
+ verify(pinnedSitesStorage).updatePinnedSite(
+ pinnedSite,
+ "Wiki",
+ "https://en.wikipedia.org/wiki/Wiki",
+ )
+ }
+
+ @Test
+ fun `WHEN getTopSites is called THEN the appropriate top sites are returned`() = runTest {
+ val defaultTopSitesStorage = DefaultTopSitesStorage(
+ pinnedSitesStorage,
+ coroutineContext,
+ )
+
+ val pinnedSite1 = TopSite.Pinned(
+ id = 2,
+ title = "Wikipedia",
+ url = "https://wikipedia.com",
+ createdAt = 2,
+ )
+ val pinnedSite2 = TopSite.Pinned(
+ id = 3,
+ title = "Example",
+ url = "https://example.com",
+ createdAt = 3,
+ )
+
+ whenever(pinnedSitesStorage.getPinnedSites()).thenReturn(
+ listOf(
+ pinnedSite1,
+ pinnedSite2,
+ ),
+ )
+
+ var topSites = defaultTopSitesStorage.getTopSites(
+ totalSites = 0,
+ frecencyConfig = null,
+ )
+
+ assertTrue(topSites.isEmpty())
+
+ topSites = defaultTopSitesStorage.getTopSites(totalSites = 1)
+
+ assertEquals(1, topSites.size)
+ assertEquals(pinnedSite1, topSites[0])
+
+ topSites = defaultTopSitesStorage.getTopSites(totalSites = 2)
+
+ assertEquals(2, topSites.size)
+ assertEquals(pinnedSite1, topSites[0])
+ assertEquals(pinnedSite2, topSites[1])
+
+ topSites = defaultTopSitesStorage.getTopSites(totalSites = 5)
+
+ assertEquals(2, topSites.size)
+ assertEquals(pinnedSite1, topSites[0])
+ assertEquals(pinnedSite2, topSites[1])
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/IntentUtilsTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/IntentUtilsTest.kt
new file mode 100644
index 0000000000..dcbfd876a4
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/IntentUtilsTest.kt
@@ -0,0 +1,21 @@
+/* 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.focus.utils
+
+import android.app.PendingIntent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class IntentUtilsTest {
+
+ @Test
+ fun `given a build version lower than 23, when defaultIntentPendingFlags is called, then flag 0 should be returned`() {
+ assertEquals(0, IntentUtils.defaultIntentPendingFlags(22))
+ }
+
+ @Test
+ fun `given a build version bigger than 22, when defaultIntentPendingFlags is called, then flag FLAG_IMMUTABLE should be returned`() {
+ assertEquals(PendingIntent.FLAG_IMMUTABLE, IntentUtils.defaultIntentPendingFlags(23))
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/SupportUtilsTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/SupportUtilsTest.kt
new file mode 100644
index 0000000000..fe55d8d4f8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/utils/SupportUtilsTest.kt
@@ -0,0 +1,64 @@
+/* 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.focus.utils
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.util.Locale
+
+class SupportUtilsTest {
+
+ @Test
+ fun cleanup() {
+ // Other tests might get confused by our locale fiddling, so lets go back to the default:
+ Locale.setDefault(Locale.ENGLISH)
+ }
+
+ /*
+ * Super simple sumo URL test - it exists primarily to verify that we're setting the language
+ * and page tags correctly. appVersion is null in tests, so we just test that there's a null there,
+ * which doesn't seem too useful...
+ */
+ @Test
+ @Throws(Exception::class)
+ fun getSumoURLForTopic() {
+ val versionName = "testVersion"
+
+ val testTopic = SupportUtils.SumoTopic.TRACKERS
+ val testTopicStr = testTopic.topicStr
+
+ Locale.setDefault(Locale.GERMANY)
+ assertEquals(
+ "https://support.mozilla.org/1/mobile/$versionName/Android/de-DE/$testTopicStr",
+ SupportUtils.getSumoURLForTopic(versionName, testTopic),
+ )
+
+ Locale.setDefault(Locale.CANADA_FRENCH)
+ assertEquals(
+ "https://support.mozilla.org/1/mobile/$versionName/Android/fr-CA/$testTopicStr",
+ SupportUtils.getSumoURLForTopic(versionName, testTopic),
+ )
+ }
+
+ /**
+ * This is a pretty boring tests - it exists primarily to verify that we're actually setting
+ * a langtag in the manfiesto URL.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun getManifestoURL() {
+ Locale.setDefault(Locale.UK)
+ assertEquals(
+ "https://www.mozilla.org/en-GB/about/manifesto/",
+ SupportUtils.manifestoURL,
+ )
+
+ Locale.setDefault(Locale.KOREA)
+ assertEquals(
+ "https://www.mozilla.org/ko-KR/about/manifesto/",
+ SupportUtils.manifestoURL,
+ )
+ }
+}
diff --git a/mobile/android/focus-android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/mobile/android/focus-android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000..cf1c399ea8
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1,2 @@
+mock-maker-inline
+// This allows mocking final classes (classes are final by default in Kotlin)
diff --git a/mobile/android/focus-android/app/src/test/resources/robolectric.properties b/mobile/android/focus-android/app/src/test/resources/robolectric.properties
new file mode 100644
index 0000000000..4359826c57
--- /dev/null
+++ b/mobile/android/focus-android/app/src/test/resources/robolectric.properties
@@ -0,0 +1,3 @@
+# Needed until Robolectric supports SDK 29+
+sdk=28
+application=org.mozilla.focus.EmptyFocusApplication
diff --git a/mobile/android/focus-android/app/tags.yaml b/mobile/android/focus-android/app/tags.yaml
new file mode 100644
index 0000000000..dfaa86e4c6
--- /dev/null
+++ b/mobile/android/focus-android/app/tags.yaml
@@ -0,0 +1,175 @@
+# 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/.
+
+
+### This file was AUTOMATICALLY GENERATED by `./tools/update-glean-tags.py`
+### DO NOT edit it by hand.
+
+# Disable line-length rule because the links in the descriptions can be long
+# yamllint disable rule:line-length
+
+
+---
+$schema: moz://mozilla.org/schemas/glean/tags/1-0-0
+Accounts:
+ description: Corresponds to the [Feature:Accounts](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AAccounts)
+ label on GitHub.
+AndroidIntegration:
+ description: Corresponds to the [Feature:AndroidIntegration](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AAndroidIntegration)
+ label on GitHub.
+Battery:
+ description: Corresponds to the [Feature:Battery](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ABattery)
+ label on GitHub.
+Bookmarks:
+ description: Corresponds to the [Feature:Bookmarks](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ABookmarks)
+ label on GitHub.
+Browsing:
+ description: Issues related to browsing experience, browser navigation, not web
+ issues. Corresponds to the [Feature:Browsing](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ABrowsing)
+ label on GitHub.
+Collections:
+ description: Corresponds to the [Feature:Collections](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ACollections)
+ label on GitHub.
+ContextMenu:
+ description: Menu that appears when long-pressing on website content. Corresponds
+ to the [Feature:ContextMenu](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AContextMenu)
+ label on GitHub.
+CrashReporting:
+ description: Corresponds to the [Feature:CrashReporting](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ACrashReporting)
+ label on GitHub.
+CustomTabs:
+ description: Corresponds to the [Feature:CustomTabs](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ACustomTabs)
+ label on GitHub.
+Download:
+ description: Corresponds to the [Feature:Download](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ADownload)
+ label on GitHub.
+ErrorMessages:
+ description: Corresponds to the [Feature:ErrorMessages](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AErrorMessages)
+ label on GitHub.
+Experiments:
+ description: A category for experiment-related features.. Corresponds to the [Feature:Experiments](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AExperiments)
+ label on GitHub.
+FindBar:
+ description: Corresponds to the [Feature:FindBar](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AFindBar)
+ label on GitHub.
+Forms:
+ description: Corresponds to the [Feature:Forms](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AForms)
+ label on GitHub.
+Gesture:
+ description: Corresponds to the [Feature:Gesture](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AGesture)
+ label on GitHub.
+History:
+ description: Corresponds to the [Feature:History](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AHistory)
+ label on GitHub.
+HomeScreen:
+ description: Corresponds to the [Feature:HomeScreen](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AHomeScreen)
+ label on GitHub.
+IME:
+ description: Text entry and keyboards. Corresponds to the [Feature:IME](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AIME)
+ label on GitHub.
+Logins:
+ description: Corresponds to the [Feature:Logins](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ALogins)
+ label on GitHub.
+MainMenu:
+ description: The three-dot menu that is seen on the browser and homescreen.. Corresponds
+ to the [Feature:MainMenu](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AMainMenu)
+ label on GitHub.
+Media:
+ description: Corresponds to the [Feature:Media](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AMedia)
+ label on GitHub.
+Migration:
+ description: Corresponds to the [Feature:Migration](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AMigration)
+ label on GitHub.
+Notifications:
+ description: Corresponds to the [Feature:Notifications](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ANotifications)
+ label on GitHub.
+Offline:
+ description: Corresponds to the [Feature:Offline](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AOffline)
+ label on GitHub.
+Onboarding:
+ description: First Run, Contextual Feature Recommendation/Recommender CFR. Corresponds
+ to the [Feature:Onboarding](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AOnboarding)
+ label on GitHub.
+OpenInApp:
+ description: intents. Corresponds to the [Feature:OpenInApp](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AOpenInApp)
+ label on GitHub.
+PWA:
+ description: Corresponds to the [Feature:PWA](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3APWA)
+ label on GitHub.
+Performance:
+ description: Used for data reviews to label metrics related to performance. Corresponds
+ to the [Feature:Performance](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3APerformance)
+ label on GitHub.
+Privacy&Security:
+ description: Corresponds to the [Feature:Privacy&Security](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3APrivacy%26Security)
+ label on GitHub.
+Push:
+ description: WebPush or Marketing push related features.. Corresponds to the [Feature:Push](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3APush)
+ label on GitHub.
+QRCode:
+ description: Corresponds to the [Feature:QRCode](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AQRCode)
+ label on GitHub.
+QuickSettings:
+ description: Corresponds to the [Feature:QuickSettings](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AQuickSettings)
+ label on GitHub.
+ReaderMode:
+ description: Corresponds to the [Feature:ReaderMode](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AReaderMode)
+ label on GitHub.
+Search:
+ description: Corresponds to the [Feature:Search](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASearch)
+ label on GitHub.
+SearchProvider:
+ description: Corresponds to the [Feature:SearchProvider](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASearchProvider)
+ label on GitHub.
+SearchWidget:
+ description: Issues related to the search widget. Corresponds to the [Feature:SearchWidget](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASearchWidget)
+ label on GitHub.
+SendTab:
+ description: Corresponds to the [Feature:SendTab](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASendTab)
+ label on GitHub.
+Settings:
+ description: Corresponds to the [Feature:Settings](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASettings)
+ label on GitHub.
+Sharing:
+ description: Corresponds to the [Feature:Sharing](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASharing)
+ label on GitHub.
+Shortcuts:
+ description: Top Sites on the Focus home page. Corresponds to the [Feature:Shortcuts](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AShortcuts)
+ label on GitHub.
+SitePermissions:
+ description: Corresponds to the [Feature:SitePermissions](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASitePermissions)
+ label on GitHub.
+Snackbar:
+ description: Corresponds to the [Feature:Snackbar](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASnackbar)
+ label on GitHub.
+Sync:
+ description: Corresponds to the [Feature:Sync](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASync)
+ label on GitHub.
+SyncTabs:
+ description: Corresponds to the [Feature:SyncTabs](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ASyncTabs)
+ label on GitHub.
+Tabs:
+ description: Corresponds to the [Feature:Tabs](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ATabs)
+ label on GitHub.
+Telemetry:
+ description: Corresponds to the [Feature:Telemetry](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ATelemetry)
+ label on GitHub.
+TextSelection:
+ description: Corresponds to the [Feature:TextSelection](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ATextSelection)
+ label on GitHub.
+Themes:
+ description: Dark mode, light mode. Corresponds to the [Feature:Themes](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AThemes)
+ label on GitHub.
+Toolbar:
+ description: Address bar, see also Feature:Search. Corresponds to the [Feature:Toolbar](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AToolbar)
+ label on GitHub.
+TrackingProtection:
+ description: Corresponds to the [Feature:TrackingProtection](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3ATrackingProtection)
+ label on GitHub.
+Voice:
+ description: Corresponds to the [Feature:Voice](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AVoice)
+ label on GitHub.
+WebExtensions:
+ description: Corresponds to the [Feature:WebExtensions](https://github.com/mozilla-mobile/focus-android/issues?q=label%3AFeature%3AWebExtensions)
+ label on GitHub.
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/copy-robo-crash-artifacts.py b/mobile/android/focus-android/automation/taskcluster/androidTest/copy-robo-crash-artifacts.py
new file mode 100644
index 0000000000..9945f08ef6
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/copy-robo-crash-artifacts.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python3
+
+# 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/.
+
+"""
+This script is designed to automate the process of fetching crash logs from Google Cloud Storage (GCS) for failed devices in a Robo test.
+It is intended to be run as part of a Taskcluster job following a scheduled test task.
+The script requires the presence of a `matrix_ids.json` artifact in the results directory and the availability of the `gsutil` command in the environment.
+
+The script performs the following operations:
+- Loads the `matrix_ids.json` artifact to identify the GCS paths for the crash logs.
+- Identifies failed devices based on the outcomes specified in the `matrix_ids.json` artifact.
+- Fetches crash logs for the failed devices from the specified GCS paths.
+- Copies the fetched crash logs to the current worker artifact results directory.
+
+The script is configured to log its operations and errors, providing visibility into its execution process.
+It uses the `gsutil` command-line tool to interact with GCS, ensuring compatibility with the GCS environment.
+
+Usage:
+ python3 script_name.py
+
+Requirements:
+ - The `matrix_ids.json` artifact must be present in the results directory.
+ - The `gsutil` command must be available in the environment.
+ - The script should be run after a scheduled test task in a Taskcluster job.
+
+Output:
+ - Crash logs for failed devices are copied to the current worker artifact results directory.
+"""
+
+import json
+import logging
+import os
+import re
+import subprocess
+import sys
+from enum import Enum
+
+
+def setup_logging():
+ """Configure logging for the script."""
+ log_format = "%(message)s"
+ logging.basicConfig(level=logging.INFO, format=log_format)
+
+
+class Worker(Enum):
+ """
+ Worker paths
+ """
+
+ RESULTS_DIR = "/builds/worker/artifacts/results"
+ ARTIFACTS_DIR = "/builds/worker/artifacts"
+
+
+class ArtifactType(Enum):
+ """
+ Artifact types for fetching crash logs and matrix IDs artifact.
+ """
+
+ CRASH_LOG = "data_app_crash*.txt"
+ MATRIX_IDS = "matrix_ids.json"
+
+
+def load_matrix_ids_artifact(matrix_file_path):
+ """Load the matrix IDs artifact from the specified file path.
+
+ Args:
+ matrix_file_path (str): The file path to the matrix IDs artifact.
+ Returns:
+ dict: The contents of the matrix IDs artifact.
+ """
+ try:
+ with open(matrix_file_path, "r") as f:
+ return json.load(f)
+ except FileNotFoundError:
+ logging.error(f"Could not find matrix file: {matrix_file_path}")
+ sys.exit(1)
+ except json.JSONDecodeError:
+ logging.error(f"Error decoding matrix file: {matrix_file_path}")
+ sys.exit(1)
+
+
+def get_gcs_path(matrix_artifact_file):
+ """
+ Extract the root GCS path from the matrix artifact file.
+
+ Args:
+ matrix_artifact_file (dict): The matrix artifact file contents.
+ Returns:
+ str: The root GCS path extracted from the matrix artifact file.
+ """
+ for matrix in matrix_artifact_file.values():
+ gcs_path = matrix.get("gcsPath")
+ if gcs_path:
+ return gcs_path
+ return None
+
+
+def check_gsutil_availability():
+ """
+ Check the availability of the `gsutil` command in the environment.
+ Exit the script if `gsutil` is not available.
+ """
+ try:
+ result = subprocess.run(["gsutil", "--version"], capture_output=True, text=True)
+ if result.returncode != 0:
+ logging.error(f"Error executing gsutil: {result.stderr}")
+ sys.exit(1)
+ except Exception as e:
+ logging.error(f"Error executing gsutil: {e}")
+ sys.exit(1)
+
+
+def fetch_artifacts(root_gcs_path, device, artifact_pattern):
+ """
+ Fetch artifacts from the specified GCS path pattern for the given device.
+
+ Args:
+ root_gcs_path (str): The root GCS path for the artifacts.
+ device (str): The device name for which to fetch artifacts.
+ artifact_pattern (str): The pattern to match the artifacts.
+ Returns:
+ list: A list of artifacts matching the specified pattern.
+ """
+ gcs_path_pattern = f"gs://{root_gcs_path.rstrip('/')}/{device}/{artifact_pattern}"
+
+ try:
+ result = subprocess.run(
+ ["gsutil", "ls", gcs_path_pattern], capture_output=True, text=True
+ )
+ if result.returncode == 0:
+ return result.stdout.splitlines()
+ else:
+ if "AccessDeniedException" in result.stderr:
+ logging.error(f"Permission denied for GCS path: {gcs_path_pattern}")
+ elif "network error" in result.stderr.lower():
+ logging.error(f"Network error accessing GCS path: {gcs_path_pattern}")
+ else:
+ logging.error(f"Failed to list files: {result.stderr}")
+ return []
+ except Exception as e:
+ logging.error(f"Error executing gsutil: {e}")
+ return []
+
+
+def fetch_failed_device_names(matrix_artifact_file):
+ """
+ Fetch the names of devices that failed based on the outcomes specified in the matrix artifact file.
+
+ Args:
+ matrix_artifact_file (dict): The matrix artifact file contents.
+ Returns:
+ list: A list of device names that failed in the test.
+ """
+ failed_devices = []
+ for matrix in matrix_artifact_file.values():
+ axes = matrix.get("axes", [])
+ for axis in axes:
+ if axis.get("outcome") == "failure":
+ device = axis.get("device")
+ if device:
+ failed_devices.append(device)
+ return failed_devices
+
+
+def gsutil_cp(artifact, dest):
+ """
+ Copy the specified artifact to the destination path using `gsutil`.
+
+ Args:
+ artifact (str): The path to the artifact to copy.
+ dest (str): The destination path to copy the artifact to.
+ Returns:
+ None
+ """
+ logging.info(f"Copying {artifact} to {dest}")
+ try:
+ result = subprocess.run(
+ ["gsutil", "cp", artifact, dest], capture_output=True, text=True
+ )
+ if result.returncode != 0:
+ if "AccessDeniedException" in result.stderr:
+ logging.error(f"Permission denied for GCS path: {artifact}")
+ elif "network error" in result.stderr.lower():
+ logging.error(f"Network error accessing GCS path: {artifact}")
+ else:
+ logging.error(f"Failed to list files: {result.stderr}")
+ except Exception as e:
+ logging.error(f"Error executing gsutil: {e}")
+
+
+def parse_crash_log(log_path):
+ crashes_reported = 0
+ if os.path.isfile(log_path):
+ with open(log_path) as f:
+ contents = f.read()
+ proc = "unknown"
+ match = re.search(r"Process: (.*)\n", contents, re.MULTILINE)
+ if match and len(match.groups()) == 1:
+ proc = match.group(1)
+ # Isolate the crash stack and reformat it for treeherder.
+ # Variation in stacks makes the regex tricky!
+ # Example:
+ # java.lang.NullPointerException
+ # at org.mozilla.fenix.library.bookmarks.BookmarkFragment.getBookmarkInteractor(BookmarkFragment.kt:72)
+ # at org.mozilla.fenix.library.bookmarks.BookmarkFragment.refreshBookmarks(BookmarkFragment.kt:297) ...
+ # Example:
+ # java.lang.IllegalStateException: pending state not allowed
+ # at org.mozilla.fenix.onboarding.OnboardingFragment.onCreate(OnboardingFragment.kt:83)
+ # at androidx.fragment.app.Fragment.performCreate(Fragment.java:3094) ...
+ # Example:
+ # java.lang.IllegalArgumentException: No handler given, and current thread has no looper!
+ # at android.hardware.camera2.impl.CameraDeviceImpl.checkHandler(CameraDeviceImpl.java:2380)
+ # at android.hardware.camera2.impl.CameraDeviceImpl.checkHandler(CameraDeviceImpl.java:2395)
+ match = re.search(
+ r"\n([\w\.]+[:\s\w\.,!?#^\'\"]+)\s*(at\s.*\n)", contents, re.MULTILINE
+ )
+ if match and len(match.groups()) == 2:
+ top_frame = match.group(1).rstrip() + " " + match.group(2)
+ remainder = contents[match.span()[1] :]
+ logging.error(f"PROCESS-CRASH | {proc} | {top_frame}{remainder}")
+ crashes_reported = 1
+ return crashes_reported
+
+
+def process_artifacts(artifact_type):
+ """
+ Process the artifacts based on the specified artifact type.
+
+ Args:
+ artifact_type (ArtifactType): The type of artifact to process.
+ Returns:
+ Number of crashes reported in treeherder format.
+ """
+ matrix_ids_artifact = load_matrix_ids_artifact(
+ Worker.RESULTS_DIR.value + "/" + ArtifactType.MATRIX_IDS.value
+ )
+ failed_device_names = fetch_failed_device_names(matrix_ids_artifact)
+ root_gcs_path = get_gcs_path(matrix_ids_artifact)
+
+ if not root_gcs_path:
+ logging.error("Could not find root GCS path in matrix file.")
+ return 0
+
+ if not failed_device_names:
+ return 0
+
+ crashes_reported = 0
+ for device in failed_device_names:
+ artifacts = fetch_artifacts(root_gcs_path, device, artifact_type.value)
+ if not artifacts:
+ logging.info(f"No artifacts found for device: {device}")
+ continue
+
+ for artifact in artifacts:
+ gsutil_cp(artifact, Worker.RESULTS_DIR.value)
+ crashes_reported += parse_crash_log(
+ os.path.join(Worker.RESULTS_DIR.value, os.path.basename(artifact))
+ )
+
+ return crashes_reported
+
+
+def main():
+ setup_logging()
+ check_gsutil_availability()
+ return process_artifacts(ArtifactType.CRASH_LOG)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-beta.yml b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-beta.yml
new file mode 100644
index 0000000000..8a3fea6dd4
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-beta.yml
@@ -0,0 +1,36 @@
+# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
+# Flank Documentation: https://flank.github.io/flank/
+gcloud:
+ results-bucket: focus_android_test_artifacts
+ record-video: true
+
+ timeout: 30m
+ async: false
+ num-flaky-test-attempts: 1
+
+ app: /app/path
+ test: /test/path
+
+ auto-google-login: false
+ use-orchestrator: true
+ environment-variables:
+ clearPackageData: true
+ directories-to-pull:
+ - /sdcard/screenshots
+ performance-metrics: true
+
+ test-targets:
+ - class org.mozilla.focus.activity.SearchTest#testBlankSearchDoesNothing
+ - class org.mozilla.focus.activity.EraseBrowsingDataTest#deleteHistoryOnRestartTest
+
+ device:
+ - model: Pixel2.arm
+ version: 30
+ locale: en_US
+
+flank:
+ project: GOOGLE_PROJECT
+ max-test-shards: 1
+ num-test-runs: 1
+ output-style: compact
+ full-junit-result: true
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test-robo.yml b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test-robo.yml
new file mode 100644
index 0000000000..dda08757fc
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test-robo.yml
@@ -0,0 +1,27 @@
+# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
+# Flank Documentation: https://flank.github.io/flank/
+gcloud:
+ results-bucket: focus_android_test_artifacts
+ record-video: false
+ timeout: 5m
+ async: false
+
+ app: /app/path
+
+ auto-google-login: false
+ use-orchestrator: true
+ environment-variables:
+ clearPackageData: true
+
+ device:
+ - model: MediumPhone.arm
+ version: 30
+ locale: en_US
+
+ type: robo
+
+flank:
+ project: GOOGLE_PROJECT
+ num-test-runs: 1
+ output-style: compact
+ full-junit-result: true
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test.yml b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test.yml
new file mode 100644
index 0000000000..8a3fea6dd4
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm-start-test.yml
@@ -0,0 +1,36 @@
+# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
+# Flank Documentation: https://flank.github.io/flank/
+gcloud:
+ results-bucket: focus_android_test_artifacts
+ record-video: true
+
+ timeout: 30m
+ async: false
+ num-flaky-test-attempts: 1
+
+ app: /app/path
+ test: /test/path
+
+ auto-google-login: false
+ use-orchestrator: true
+ environment-variables:
+ clearPackageData: true
+ directories-to-pull:
+ - /sdcard/screenshots
+ performance-metrics: true
+
+ test-targets:
+ - class org.mozilla.focus.activity.SearchTest#testBlankSearchDoesNothing
+ - class org.mozilla.focus.activity.EraseBrowsingDataTest#deleteHistoryOnRestartTest
+
+ device:
+ - model: Pixel2.arm
+ version: 30
+ locale: en_US
+
+flank:
+ project: GOOGLE_PROJECT
+ max-test-shards: 1
+ num-test-runs: 1
+ output-style: compact
+ full-junit-result: true
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm64-v8a.yml b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm64-v8a.yml
new file mode 100644
index 0000000000..1439d6c761
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-arm64-v8a.yml
@@ -0,0 +1,36 @@
+# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
+# Flank Documentation: https://flank.github.io/flank/
+gcloud:
+ results-bucket: focus_android_test_artifacts
+ record-video: true
+
+ timeout: 30m
+ async: false
+ num-flaky-test-attempts: 1
+
+ app: /app/path
+ test: /test/path
+
+ auto-google-login: false
+ use-orchestrator: true
+ environment-variables:
+ clearPackageData: true
+ directories-to-pull:
+ - /sdcard/screenshots
+ performance-metrics: true
+
+ test-targets:
+ - package org.mozilla.focus.activity
+ - package org.mozilla.focus.privacy
+
+ device:
+ - model: Pixel2.arm
+ version: 30
+ locale: en_US
+
+flank:
+ project: GOOGLE_PROJECT
+ max-test-shards: 50
+ num-test-runs: 1
+ output-style: compact
+ full-junit-result: true
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/flank-x86.yml b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-x86.yml
new file mode 100644
index 0000000000..83a745928a
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/flank-x86.yml
@@ -0,0 +1,36 @@
+# Google Cloud Documentation: https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
+# Flank Documentation: https://flank.github.io/flank/
+gcloud:
+ results-bucket: focus_android_test_artifacts
+ record-video: true
+
+ timeout: 30m
+ async: false
+ num-flaky-test-attempts: 1
+
+ app: /app/path
+ test: /test/path
+
+ auto-google-login: false
+ use-orchestrator: true
+ environment-variables:
+ clearPackageData: true
+ directories-to-pull:
+ - /sdcard/screenshots
+ performance-metrics: true
+
+ test-targets:
+ - package org.mozilla.focus.activity
+ - package org.mozilla.focus.privacy
+
+ device:
+ - model: Pixel2
+ version: 30
+ locale: en_US
+
+flank:
+ project: GOOGLE_PROJECT
+ max-test-shards: -1
+ num-test-runs: 1
+ output-style: compact
+ full-junit-result: true
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test-fromfile.py b/mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test-fromfile.py
new file mode 100644
index 0000000000..7b853d5e9c
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test-fromfile.py
@@ -0,0 +1,97 @@
+#!/usr/bin/python3
+
+import argparse
+import sys
+import xml
+from pathlib import Path
+
+from beautifultable import BeautifulTable
+from junitparser import Attr, Failure, JUnitXml, TestSuite
+
+
+def parse_args(cmdln_args):
+ parser = argparse.ArgumentParser(
+ description="Parse and print UI test JUnit results"
+ )
+ parser.add_argument(
+ "--results",
+ type=Path,
+ help="Directory containing task artifact results",
+ required=True,
+ )
+ return parser.parse_args(args=cmdln_args)
+
+
+class test_suite(TestSuite):
+ flakes = Attr()
+
+
+def parse_print_failure_results(results):
+ table = BeautifulTable(maxwidth=256)
+ table.columns.header = ["UI Test", "Outcome", "Details"]
+ table.columns.alignment = BeautifulTable.ALIGN_LEFT
+ table.set_style(BeautifulTable.STYLE_GRID)
+
+ failure_count = 0
+ for suite in results:
+ cur_suite = test_suite.fromelem(suite)
+ if cur_suite.flakes != "0":
+ for case in suite:
+ for entry in case.result:
+ if case.result:
+ table.rows.append(
+ [
+ "%s#%s" % (case.classname, case.name),
+ "Flaky",
+ entry.text.replace("\t", " "),
+ ]
+ )
+ break
+ else:
+ for case in suite:
+ for entry in case.result:
+ if isinstance(entry, Failure):
+ test_id = "%s#%s" % (case.classname, case.name)
+ details = entry.text.replace("\t", " ")
+ table.rows.append(
+ [
+ test_id,
+ "Failure",
+ details,
+ ]
+ )
+ print(f"TEST-UNEXPECTED-FAIL | {test_id} | {details}")
+ failure_count += 1
+ break
+ print(table)
+ return failure_count
+
+
+def load_results_file(filename):
+ ret = None
+ try:
+ f = open(filename, "r")
+ try:
+ ret = JUnitXml.fromfile(f)
+ except xml.etree.ElementTree.ParseError as e:
+ print(f"Error parsing {filename} file: {e}")
+ finally:
+ f.close()
+ except IOError as e:
+ print(e)
+
+ return ret
+
+
+def main():
+ args = parse_args(sys.argv[1:])
+
+ failure_count = 0
+ junitxml = load_results_file(args.results.joinpath("FullJUnitReport.xml"))
+ if junitxml:
+ failure_count = parse_print_failure_results(junitxml)
+ return failure_count
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test.py b/mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test.py
new file mode 100644
index 0000000000..de41a6c7e7
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/parse-ui-test.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python3
+
+from __future__ import print_function
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+import yaml
+
+
+def parse_args(cmdln_args):
+ parser = argparse.ArgumentParser(description="Parse UI test logs an results")
+ parser.add_argument(
+ "--output-md",
+ type=argparse.FileType("w", encoding="utf-8"),
+ help="Output markdown file.",
+ required=True,
+ )
+ parser.add_argument(
+ "--log",
+ type=argparse.FileType("r", encoding="utf-8"),
+ help="Log output of flank.",
+ required=True,
+ )
+ parser.add_argument(
+ "--results", type=Path, help="Directory containing flank results", required=True
+ )
+ parser.add_argument(
+ "--exit-code", type=int, help="Exit code of flank.", required=True
+ )
+ parser.add_argument("--device-type", help="Type of device ", required=True)
+ parser.add_argument(
+ "--report-treeherder-failures",
+ help="Report failures in treeherder format.",
+ required=False,
+ action="store_true",
+ )
+ return parser.parse_args(args=cmdln_args)
+
+
+def extract_android_args(log):
+ return yaml.safe_load(log.split("AndroidArgs\n")[1].split("RunTests\n")[0])
+
+
+def main():
+ args = parse_args(sys.argv[1:])
+
+ log = args.log.read()
+ matrix_ids = json.loads(args.results.joinpath("matrix_ids.json").read_text())
+
+ android_args = extract_android_args(log)
+
+ print = args.output_md.write
+
+ print("# Devices\n")
+ print(yaml.safe_dump(android_args["gcloud"]["device"]))
+
+ print("# Results\n")
+ print("| Matrix | Result | Firebase Test Lab | Details\n")
+ print("| --- | --- | --- | --- |\n")
+ for matrix, matrix_result in matrix_ids.items():
+ for axis in matrix_result["axes"]:
+ print(
+ f"| {matrix_result['matrixId']} | {matrix_result['outcome']}"
+ f"| [Firebase Test Lab]({matrix_result['webLink']}) | {axis['details']}\n"
+ )
+ if (
+ args.report_treeherder_failures
+ and matrix_result["outcome"] != "success"
+ and matrix_result["outcome"] != "flaky"
+ ):
+ # write failures to test log in format known to treeherder logviewer
+ sys.stdout.write(
+ f"TEST-UNEXPECTED-FAIL | {matrix_result['outcome']} | {matrix_result['webLink']} | {axis['details']}\n"
+ )
+ print("---\n")
+ print("# References & Documentation\n")
+ print(
+ "* [Automated UI Testing Documentation](https://github.com/mozilla-mobile/shared-docs/blob/main/android/ui-testing.md)\n"
+ )
+ print(
+ "* Mobile Test Engineering on [Confluence](https://mozilla-hub.atlassian.net/wiki/spaces/MTE/overview) | [Slack](https://mozilla.slack.com/archives/C02KDDS9QM9) | [Alerts](https://mozilla.slack.com/archives/C0134KJ4JHL)\n"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/robo-test.sh b/mobile/android/focus-android/automation/taskcluster/androidTest/robo-test.sh
new file mode 100755
index 0000000000..c2b6ee76f9
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/robo-test.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+# 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/.
+
+# This script does the following:
+# 1. Retrieves gcloud service account token
+# 2. Activates gcloud service account
+# 3. Connects to Google's Firebase Test Lab (using Flank)
+# 4. Executes Robo test
+# 5. Puts any artifacts into the test_artifacts folder
+
+# If a command fails then do not proceed and fail this script too.
+set -e
+
+get_abs_filename() {
+ relative_filename="$1"
+ echo "$(cd "$(dirname "$relative_filename")" && pwd)/$(basename "$relative_filename")"
+}
+
+# Basic parameter check
+if [[ $# -lt 1 ]]; then
+ echo "Error: please provide a Flank configuration"
+ display_help
+ exit 1
+fi
+
+device_type="$1" # flank-arm-robo-test.yml | flank-x86-robo-test.yml
+APK_APP="$2"
+JAVA_BIN="/usr/bin/java"
+PATH_TEST="./automation/taskcluster/androidTest"
+FLANK_BIN="/builds/worker/test-tools/flank.jar"
+ARTIFACT_DIR="/builds/worker/artifacts"
+RESULTS_DIR="${ARTIFACT_DIR}/results"
+
+echo
+echo "ACTIVATE SERVICE ACCT"
+echo
+gcloud config set project "$GOOGLE_PROJECT"
+gcloud auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS"
+echo
+echo
+echo
+
+set +e
+
+flank_template="${PATH_TEST}/flank-${device_type}.yml"
+if [ -f "$flank_template" ]; then
+ echo "Using Flank template: $flank_template"
+else
+ echo "Error: Flank template not found: $flank_template"
+ exit 1
+fi
+
+APK_APP="$(get_abs_filename $APK_APP)"
+
+function failure_check() {
+ echo
+ echo
+ if [[ $exitcode -ne 0 ]]; then
+ echo "FAILURE: Robo test run failed, please check above URL"
+ else
+ echo "Robo test was successful!"
+ fi
+
+ echo
+ echo "RESULTS"
+ echo
+
+ mkdir -p /builds/worker/artifacts/github
+ chmod +x $PATH_TEST/parse-ui-test.py
+ $PATH_TEST/parse-ui-test.py \
+ --exit-code "${exitcode}" \
+ --log flank.log \
+ --results "${RESULTS_DIR}" \
+ --output-md "${ARTIFACT_DIR}/github/customCheckRunText.md" \
+ --device-type "${device_type}"
+}
+
+echo
+echo "FLANK VERSION"
+echo
+$JAVA_BIN -jar $FLANK_BIN --version
+echo
+echo
+
+echo
+echo "EXECUTE ROBO TEST"
+echo
+set -o pipefail && $JAVA_BIN -jar $FLANK_BIN android run \
+ --config=$flank_template \
+ --app=$APK_APP \
+ --local-result-dir="${RESULTS_DIR}" \
+ --project=$GOOGLE_PROJECT \
+ --client-details=commit=${MOBILE_HEAD_REV:-None},pullRequest=${PULL_REQUEST_NUMBER:-None} \
+ | tee flank.log
+
+exitcode=$?
+failure_check
+
+exit $exitcode
diff --git a/mobile/android/focus-android/automation/taskcluster/androidTest/ui-test.sh b/mobile/android/focus-android/automation/taskcluster/androidTest/ui-test.sh
new file mode 100755
index 0000000000..362d528b3d
--- /dev/null
+++ b/mobile/android/focus-android/automation/taskcluster/androidTest/ui-test.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+# 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/.
+
+# This script does the following:
+# 1. Retrieves gcloud service account token
+# 2. Activates gcloud service account
+# 3. Connects to google Firebase (using TestArmada's Flank tool)
+# 4. Executes UI tests
+# 5. Puts test artifacts into the test_artifacts folder
+
+# NOTE:
+# Flank supports sharding across multiple devices at a time, but gcloud API
+# only supports 1 defined APK per test run.
+
+
+# If a command fails then do not proceed and fail this script too.
+set -e
+
+#########################
+# The command line help #
+#########################
+display_help() {
+ echo "Usage: $0 Build_Variant [Number_Shards...]"
+ echo
+ echo "Examples:"
+ echo "To run UI tests on ARM device shard (1 test / shard)"
+ echo "$ ui-test.sh arm64-v8a -1"
+ echo
+ echo "To run UI tests on X86 device (on 3 shards)"
+ echo "$ ui-test.sh x86 3"
+ echo
+}
+
+get_abs_filename() {
+ relative_filename="$1"
+ echo "$(cd "$(dirname "$relative_filename")" && pwd)/$(basename "$relative_filename")"
+}
+
+
+# Basic parameter check
+if [[ $# -lt 1 ]]; then
+ echo "Error: please provide at least one build variant (arm|x86)"
+ display_help
+ exit 1
+fi
+
+device_type="$1" # arm64-v8a | armeabi-v7a | x86_64 | x86
+APK_APP="$2"
+APK_TEST="$3"
+if [[ ! -z "$4" ]]; then
+ num_shards=$4
+fi
+
+JAVA_BIN="/usr/bin/java"
+PATH_TEST="./automation/taskcluster/androidTest"
+FLANK_BIN="/builds/worker/test-tools/flank.jar"
+ARTIFACT_DIR="/builds/worker/artifacts"
+RESULTS_DIR="${ARTIFACT_DIR}/results"
+
+echo
+echo "ACTIVATE SERVICE ACCT"
+echo
+# this is where the Google Testcloud project ID is set
+gcloud config set project "$GOOGLE_PROJECT"
+echo
+
+gcloud auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS"
+echo
+echo
+
+# Disable exiting on error. If the tests fail we want to continue
+# and try to download the artifacts. We will exit with the actual error code later.
+set +e
+
+if [[ "${device_type}" =~ ^(arm64-v8a|armeabi-v7a|x86_64|x86)$ ]]; then
+ flank_template="${PATH_TEST}/flank-${device_type}.yml"
+elif [[ "${device_type}" == "arm-start-test" ]]; then
+ flank_template="${PATH_TEST}/flank-arm-start-test.yml"
+elif [[ "${device_type}" == "arm-beta-tests" ]]; then
+ flank_template="${PATH_TEST}/flank-arm-beta.yml"
+else
+ echo "FAILURE: flank config file not found!"
+ exitcode=1
+fi
+
+APK_APP="$(get_abs_filename $APK_APP)"
+APK_TEST="$(get_abs_filename $APK_TEST)"
+echo "device_type: ${device_type}"
+echo "APK_APP: ${APK_APP}"
+echo "APK_TEST: ${APK_TEST}"
+
+# function to exit script with exit code from test run.
+# (Only 0 if all test executions passed)
+function failure_check() {
+ echo
+ echo
+ if [[ $exitcode -ne 0 ]]; then
+ echo "FAILURE: UI test run failed, please check above URL"
+ else
+ echo "All UI test(s) have passed!"
+ fi
+ echo
+ echo "RESULTS"
+ echo
+
+ mkdir -p /builds/worker/artifacts/github
+ chmod +x $PATH_TEST/parse-ui-test.py
+ $PATH_TEST/parse-ui-test.py \
+ --exit-code "${exitcode}" \
+ --log flank.log \
+ --results "${RESULTS_DIR}" \
+ --output-md "${ARTIFACT_DIR}/github/customCheckRunText.md" \
+ --device-type "${device_type}"
+
+ chmod +x $PATH_TEST/parse-ui-test-fromfile.py
+ $PATH_TEST/parse-ui-test-fromfile.py \
+ --results "${RESULTS_DIR}"
+
+}
+
+echo
+echo "FLANK VERSION"
+echo
+$JAVA_BIN -jar $FLANK_BIN --version
+echo
+echo
+
+echo
+echo "EXECUTE TEST(S)"
+echo
+# Note that if --local-results-dir is "results", timestamped sub-directory will
+# contain the results. For any other value, the directory itself will have the results.
+set -o pipefail && $JAVA_BIN -jar $FLANK_BIN android run \
+ --config=$flank_template \
+ --max-test-shards=$num_shards \
+ --app=$APK_APP --test=$APK_TEST \
+ --local-result-dir="${RESULTS_DIR}" \
+ --project=$GOOGLE_PROJECT \
+ --client-details=commit=${MOBILE_HEAD_REV:-None},pullRequest=${PULL_REQUEST_NUMBER:-None} \
+ | tee flank.log
+
+exitcode=$?
+failure_check
+
+exit $exitcode
diff --git a/mobile/android/focus-android/build.gradle b/mobile/android/focus-android/build.gradle
new file mode 100644
index 0000000000..528b1f93cb
--- /dev/null
+++ b/mobile/android/focus-android/build.gradle
@@ -0,0 +1,181 @@
+import io.gitlab.arturbosch.detekt.Detekt
+import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
+ maven {
+ url repository
+ if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
+ allowInsecureProtocol = true
+ }
+ }
+ }
+ }
+
+ dependencies {
+ classpath ComponentsDependencies.tools_androidgradle
+ classpath ComponentsDependencies.tools_kotlingradle
+ classpath FocusDependencies.osslicenses_plugin
+ classpath "org.mozilla.telemetry:glean-gradle-plugin:${Versions.mozilla_glean}"
+ classpath "${ApplicationServicesConfig.groupId}:tooling-nimbus-gradle:${ApplicationServicesConfig.version}"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+
+ // Variables in plugins {} aren't directly supported. Hack around it by setting an
+ // intermediate variable which can pull from FocusDependenciesPlugin.kt and be used later.
+ ext {
+ detekt_plugin = Versions.detekt
+ python_envs_plugin = Versions.python_envs_plugin
+ ksp_plugin = Versions.ksp_plugin
+ }
+}
+
+plugins {
+ id "io.gitlab.arturbosch.detekt" version "$detekt_plugin"
+ id("com.google.devtools.ksp").version("$ksp_plugin")
+}
+
+detekt {
+ input = files("$projectDir/app")
+ config = files("$projectDir/quality/detekt.yml")
+ baseline = file("$projectDir/quality/detekt-baseline.xml")
+
+ reports {
+ html {
+ enabled = true
+ destination = file("$projectDir/build/reports/detekt.html")
+ }
+ xml {
+ enabled = false
+ }
+ txt {
+ enabled = false
+ }
+ }
+}
+
+tasks.withType(Detekt).configureEach() {
+ autoCorrect = true
+
+ exclude "**/test/**"
+ exclude "**/androidTest/**"
+ exclude "**/build/**"
+ exclude "**/resources/**"
+ exclude "**/tmp/**"
+}
+
+// Apply same path exclusions as for the main task
+tasks.withType(DetektCreateBaselineTask).configureEach() {
+ exclude "**/test/**"
+ exclude "**/androidTest/**"
+ exclude "**/build/**"
+ exclude "**/resources/**"
+ exclude "**/tmp/**"
+}
+
+allprojects {
+ repositories {
+ gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
+ maven {
+ url repository
+ if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
+ allowInsecureProtocol = true
+ }
+ }
+ }
+
+ maven {
+ url "${gradle.mozconfig.topobjdir}/gradle/maven"
+ }
+ }
+}
+
+subprojects {
+ afterEvaluate {
+ kotlin {
+ jvmToolchain(config.jvmTargetCompatibility)
+ }
+ }
+
+ tasks.withType(KotlinCompile).configureEach { task ->
+ // Translate Kotlin messages like "w: ..." and "e: ..." into
+ // "...: warning: ..." and "...: error: ...", to make Treeherder understand.
+ def listener = {
+
+ if (it.startsWith("e: warnings found")) {
+ return
+ }
+
+ if (it.startsWith('w: ') || it.startsWith('e: ')) {
+ def matches = (it =~ /([ew]): (.+):(\d+):(\d+) (.*)/)
+ if (!matches) {
+ logger.quiet "kotlinc message format has changed!"
+ if (it.startsWith('w: ')) {
+ // For warnings, don't continue because we don't want to throw an
+ // exception. For errors, we want the exception so that the new error
+ // message format gets translated properly.
+ return
+ }
+ }
+ def (_, type, file, line, column, message) = matches[0]
+ type = (type == 'w') ? 'warning' : 'error'
+ // Use logger.lifecycle, which does not go through stderr again.
+ logger.lifecycle "$file:$line:$column: $type: $message"
+ }
+ } as StandardOutputListener
+
+ doFirst {
+ logging.addStandardErrorListener(listener)
+ }
+ doLast {
+ logging.removeStandardErrorListener(listener)
+ }
+ }
+}
+
+tasks.register('clean', Delete) {
+ delete rootProject.layout.buildDirectory
+}
+
+
+configurations {
+ ktlint
+}
+
+dependencies {
+ ktlint("com.pinterest:ktlint:${Versions.ktlint}") {
+ attributes {
+ attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
+ }
+ }
+}
+
+tasks.register('ktlint', JavaExec) {
+ description = "Check Kotlin code style."
+ classpath = configurations.ktlint
+ mainClass.set("com.pinterest.ktlint.Main")
+ args "app/**/*.kt", "!**/build/**/*.kt", "buildSrc/**/*.kt"
+}
+
+
+tasks.register('ktlintFormat', JavaExec) {
+ description = "Fix Kotlin code style deviations."
+ classpath = configurations.ktlint
+ mainClass.set("com.pinterest.ktlint.Main")
+ args "-F", "app/**/*.kt", "!**/build/**/*.kt", "buildSrc/**/*.kt"
+ jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
+}
+
+tasks.register("listRepositories") {
+ doLast {
+ println "Repositories:"
+ project.repositories.each { println "Name: " + it.name + "; url: " + it.url }
+ }
+}
diff --git a/mobile/android/focus-android/codecov.yml b/mobile/android/focus-android/codecov.yml
new file mode 100644
index 0000000000..d5c28d1afb
--- /dev/null
+++ b/mobile/android/focus-android/codecov.yml
@@ -0,0 +1,22 @@
+codecov:
+ max_report_age: off
+
+comment: false
+
+coverage:
+ precision: 2
+ round: down
+ status:
+ project:
+ default:
+ threshold: 0.1
+ if_not_found: success
+ patch:
+ default:
+ enabled: yes
+ threshold: 0.1
+ if_not_found: success
+ changes:
+ default:
+ enabled: no
+ if_not_found: success
diff --git a/mobile/android/focus-android/docs/Adjust-Usage.md b/mobile/android/focus-android/docs/Adjust-Usage.md
new file mode 100644
index 0000000000..03d85363f1
--- /dev/null
+++ b/mobile/android/focus-android/docs/Adjust-Usage.md
@@ -0,0 +1,66 @@
+# Adjust Usage
+
+> **If there is anything in this document that is not clear, is incorrect, or that requires more detail, please file a request through a [Github](https://github.com/mozilla-mobile/focus-android/issues) or [Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=Focus&component=General). Also feel free to submit corrections or additional information.**
+
+Firefox Focus (but not Firefox Klar) tracks certain types of installs using a third-party install tracking framework called Adjust. The intention is to determine the origin of Firefox Focus installs by answering the question, “Did this user on this device install Firefox Focus in response to a specific advertising campaign performed by Mozilla?”
+
+The framework consists of a software development kit (SDK) linked into Firefox Focus and a data-collecting Internet service backend run by the German company [adjust GmbH](https://www.adjust.com). The Adjust SDK is open source and MIT licensed. It is hosted at [https://github.com/adjust/android_sdk](https://github.com/adjust/android_sdk). Firefox Focus pulls in an unmodified copy of the SDK using Gradle. The [build.gradle](https://github.com/mozilla-mobile/focus-android/blob/master/app/build.gradle) contains the version of the framework that is being used. The SDK is documented at [https://docs.adjust.com](https://docs.adjust.com).
+
+## Adjust Integration
+
+The Adjust framework is abstracted via the [AdjustHelper](https://github.com/mozilla-mobile/focus-android/blob/master/app/src/focusRelease/java/org/mozilla/focus/utils/AdjustHelper.java) class. All interaction with Adjust happens via this class.
+
+## Adjust Messages
+
+The Adjust SDK collects and sends one type of message to the Adjust backend:
+
+* At the start of a new application session, a *Session Message* is sent with basic system info and how often the app has been used since the last time.
+
+The message is documented below in more detail of what is sent in each HTTP request. All messages are posted to a secure endpoint at `https://app.adjust.com`. They are all `application/x-www-form-urlencoded` HTTP `POST` requests.
+
+### Session Message
+
+#### Request
+
+```
+bundle_id: org.mozilla.focus
+tracking_enabled: 0
+language: en
+country: CA
+app_version: 4.2
+device_name: Pixel 2
+app_version_short: 2.0
+needs_response_details: 0
+attribution_deeplink: 1
+session_count: 1
+os_name: android
+event_buffering_enabled: 0
+idfv: 8D452BFB-0692-4E8C-9DE0-7578486A872E
+hardware_name: J127AP
+app_token: xxxxxxxxxxxx
+os_version: 10.1
+environment: production
+created_at: 2016-11-10T20:34:39.720Z-0500
+device_type: phone
+idfa: 00000000-0000-0000-0000-000000000000
+sent_at: 2016-11-10T20:34:39.787Z-0500
+```
+
+These parameters (including ones not exposed to Mozilla) are documented at [https://partners.adjust.com/placeholders/](https://partners.adjust.com/placeholders/)
+
+#### Response
+
+If the application was successfully attributed to a specific campaign, details for that campaign are sent back as a JSON response:
+
+```
+{ "app_token": "xxxxxxxxxxxx",
+ "adid": "00000000000000000000",
+ "attribution" { "tracker_token": "xxxxxx",
+ "tracker_name": "Network::CAMPAIGN::ADGROUP::CREATIVE",
+ "network": "Network",
+ "campaign":"CAMPAIGN",
+ "adgroup":"ADGROUP",
+ "creative":"CREATIVE" } }
+```
+
+The application has no use for this information and ignores it.
diff --git a/mobile/android/focus-android/docs/Architecture-Decisions.md b/mobile/android/focus-android/docs/Architecture-Decisions.md
new file mode 100644
index 0000000000..f42a5431c5
--- /dev/null
+++ b/mobile/android/focus-android/docs/Architecture-Decisions.md
@@ -0,0 +1,92 @@
+# Architecture Decisions
+
+This is a collection of records for "architecturally significant" decisions. [Why?](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)
+
+---
+
+## Observing state (LiveData)
+
+* **ADR-3**
+* Status: Accepted
+* Affected version: 2.0+
+
+### Context
+
+During the refactoring of Focus for Android to support multitasking it became apparent that our callback-based approach wasn't good enough anymore. Previously the current browser state was used to update the UI only. Now with multiple concurrent sessions this state needed to be preserved and restored while the user switched between tabs. In addition to that we wanted to update multiple UI components based on the current state (partially) for which a single callback implementation became very complex fast.
+
+Having observers for small chunks of data and a "reactive style" seemed to be a better approach. There are a lot of libraries that offer this functionality already, namely: RxJava, event buses (e.g. Otto and EventBus), Architecture Components (LiveData), Agera and others.
+
+### Decision
+
+LiveData objects from Android's Architecture Components library are used to wrap data that updates asynchronously. UI components that depend on this data can either get the current state or subscribe in order to get updates whenever the data changes.
+
+Reasons:
+
+* The Architecture Components library is written and maintained by the core Android team at Google (-> high quality and visibility)
+* Parts of the library are going to be used by the support libraries. Therefore they will get included into Focus for Android sooner or later anyways.
+* The library is pretty small.
+* The library takes care of the Activity/Fragment lifecycle (Avoids common errors).
+
+### Consequences
+
+* LiveData was designed to be used with `ViewModel` instances. So far we do not use `ViewModel`s and are also observing LiveData objects from non-UI components. This is not problematic but can cause problems in future versions of the library - although unlikely.
+
+---
+
+## Browser engine: GeckoView vs. WebView
+
+* **ADR-2**
+* Status: Accepted
+* Affected app versions: 1.0+
+
+### Context
+
+To render web content Firefox Focus needs to use a "web browser engine". So far all browsers from Mozilla, and Firefox for Android in particular, are using the [Gecko engine](https://en.wikipedia.org/wiki/Gecko_(software)).
+
+Android itself ships with a [WebView](https://developer.android.com/guide/webapps/webview.html) component that is (on new Android versions) based on Chromium/Chrome ([Blink engine](https://en.wikipedia.org/wiki/Blink_(web_engine))).
+
+A number of existing Android browsers (e.g. Opera, Brave) are built on top of the Blink rendering engine or are a direct fork of Chromium.
+
+### Decision
+
+Firefox Focus is going to be build on top of Android's WebView.
+
+Reasons for using WebView:
+* At the time of writing the Focus for Android prototype GeckoView already existed but it wasn't in a state that it could be used outside of Firefox for Android reliably. In addition to that there wasn't a stable API comparable offering the feature set of WebView.
+* APK size has been a long-term concern of the Firefox for Android team. A large APK size has been problematic for partnership deals and distribution in countries where bandwidth is limited or expensive. GeckoView is roughly 30 MB in size, while WebView is part of the Android system and is basically "free". Prototype builds of Focus for Android based on WebView were less than 3 MB in size.
+
+In addition to that there will be a build configuration that uses GeckoView. The GeckoView version will be guaranteed to compile; but keeping feature parity or keeping the build bug free is not a goal of the team. At this time the GeckoView version is a tech demo to explore its future potential only.
+
+Forking Chromium (or using Blink) was considered a too large investment in the prototype stage.
+
+### Consequences
+
+WebView has a complex API. Nevertheless it doesn't allow us to do heavy low-level customization that we could do if we would own the web browser engine (e.g. GeckoView). It remains to be seen whether this limitation will prevent feature development in a way that forces us to ship a browser engine with Focus for Android.
+
+---
+
+## Minimum supported Android version: 5.0+ (API 21+)
+
+* **ADR-1**
+* Status: Accepted
+* Affected app versions: 1.0+
+
+### Context
+
+Every app needs to define a minimum supported SDK version. This is usually a trade-off between how many users can be reached ([Android version distribution](https://developer.android.com/about/dashboards/index.html)) and what platform features the app needs to support ([Android API level overview](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels)).
+
+### Decision
+
+Focus will support Android versions 5 and higher (API 21+). This decisions is primarily driven by the following platform features that are not available on earlier versions of Android:
+
+* [WebView.shouldInterceptRequest()](shouldInterceptRequest): Our content blocking content uses the implementation of the callback that lets us inspect the request object. This implementation is only available on Android 21+.
+
+* [UI features](https://developer.android.com/training/material/shadows-clipping.html) like `elevation` allow us to build and prototype a "material" UI without needing to backport functionality.
+
+### Consequences
+
+* At the point of writing this ADR we will cover 73.4% of the Android market with that decision [(*)](https://developer.android.com/about/dashboards/index.html).
+
+* Android 4.4 (KitKat) still covers 17.1%. Those users can't install and use Focus. While UI features can be backported, the extended WebView APIs can not. Only a GeckoView based version of Focus would be able to support Android 4.4 or lower.
+
+---
diff --git a/mobile/android/focus-android/docs/Battery-Debugging.md b/mobile/android/focus-android/docs/Battery-Debugging.md
new file mode 100644
index 0000000000..e24e22e22a
--- /dev/null
+++ b/mobile/android/focus-android/docs/Battery-Debugging.md
@@ -0,0 +1,37 @@
+# Battery Debugging
+
+Android Performance Profiling can be quite tricky in spite of the wealth of good tools available. This doc is to explain how to track down battery and performance issues.
+
+## Battery Historian
+
+Whenever battery issues arise on Android, the first Google suggestion is to use the Battery Historian tool to parse an Android bug report and show what caused the battery drain.
+
+While this is a good option for most Android apps, it proves next to useless for analyzing most battery issues in Focus and Klar. This is because Focus and Klar uses a foreground service that remains running as long as your web pages are open. The service is required to keep the app in the foreground so data can remain in memory rather than being persisted to disk. This keeps the app from being killed by the OS and sending all your precious web session and page data to the garbage collector.
+
+Battery Historian starts from the premise that most battery problems are caused by excessive wake locks. It helps the programmer by showing wake locks, when the WiFi or cellular radio is being kept awake, and the rate of battery drain at different times. Focus and Klar do not require wake locks and they wouldn't be interesting, since the apps remain in the foreground most of the time the app is running.
+
+Analyzing Battery Historian results can still prove interesting. [Here are the instructions](https://developer.android.com/studio/profile/battery-historian) to try.
+
+## Android Studio Profiler
+
+If Battery Historian does not point at the radio or screen being kept alive as the issue, the next thing is to look for excessive CPU activity. There are several tools to look into this, but the easiest to get started is the profiler built into Android Studio.
+
+Open the project in Android Studio. If there is no Android Profiler tab on the bottom of the screen, open it from View -> Tool Windows -> Android Profiler.
+
+Make sure you're running a debug version of the app and choose the device and debuggable process from the dropdowns at the top of the Profiler.
+
+Click the CPU graph to get additional CPU detail. Choose instrumented to increase the resolution of the data. If the battery drain is happening while the phone is idle, you can easily check to find out what is causing the CPU activity, since there shouldn't be much at all.
+
+[Here's more](https://developer.android.com/studio/profile/android-profiler) on how to use the Android Profiler in general. [Here's more on the CPU Profiler](https://developer.android.com/studio/profile/cpu-profiler) specifically.
+
+## Systrace
+
+Systrace does not add that much to what Android Studio's Profiler already provides and the interface is a clunky webpage. You can still try it if you like. [Here are the instructions](https://developer.android.com/studio/command-line/systrace).
+
+## Traceview
+
+While you can get a lot of useful and interesting information from the Android Profiler and Systrace, it's helpful to get full call stacks. The easiest way to do this is to use the older Android profiling tool, Traceview. You can select from the code when to start and stop tracing with "Debug.startMethodTracing("name")" and "Debug.stopMethodTracing()"
+
+When you run the code, it'll generate a trace file in the files folder on the SDcard. You can read the trace file either by double-clicking it in Android Studio's Device File Explorer or even better by opening it in Android Device Monitor. You can start Android Device Monitor in recent Android Studio versions by running $ANDROID_SDK/tools/monitor.
+
+[Here's more detail on using TraceView](https://developer.android.com/studio/profile/traceview).
diff --git a/mobile/android/focus-android/docs/Content-blocking.md b/mobile/android/focus-android/docs/Content-blocking.md
new file mode 100644
index 0000000000..0b1bf45a26
--- /dev/null
+++ b/mobile/android/focus-android/docs/Content-blocking.md
@@ -0,0 +1,58 @@
+# Content blocking
+
+## Outdated Content.
+Focus is using the same technology as Firefox for desktop, all the content blocking is happening inside of the Gecko engine web engine. content blocking list can be seen here https://github.com/mozilla-services/shavar-prod-lists.
+
+## Tracking protection: lists and general overview
+
+We use the disconnect tracking protection lists, these consist of:
+
+- The deny-list: a list of domains to block
+- The entitylist: an override list to unblock certain domains for certain other domains.
+- The google_mapping: a list of modifications for the deny-list.
+
+### The deny-list
+
+The deny-list contains list of domains that should be blocked. Any resources that are hosted
+on these domains should be blocked.
+
+The source lists contain multiple categories, we use **Advertising**, **Social**, **Analytics**, **Content** (aka. "other content trackers").
+We use some items from the **Disconnect** list, those items are moved into the **Social** list when parsing. We ignore **Legacy Disconnect** and
+**Legacy Social**. You can see the code parsing these lists at
+[BlockListProcessor.java](../app/src/webkit/java/org/mozilla/focus/webkit/matcher/BlocklistProcessor.java).
+
+The google_mapping is similar to the main list - these items are simply addded to the existing categories mentioned above. (The google entries
+in the main **Disconnect** list are discarded since we use those from google_mapping.json.)
+
+Note: each category contains a list of site owners: those names are discarded, we insert every single domain for every owner into the same list.
+
+### Entitylist: the override list
+
+Some domains need to be unblocked when visiting another domain belonging to the same owner. E.g. we usually block "googleapis.com", but when visiting e.g. google.de,
+we unblock "googleapis.com". This is done in the entitylist: for every domain that belongs to a "property" in the entitylist, we unblock all domains listed in
+"resources".
+
+## Implementation
+
+WebView calls the WebViewClient.shouldInterceptRequest() callback every time a resource is to be loaded - this permits us to intercept resource loading, and is how we
+can perform content blocking on Android. (See [BlockListProcessor.java](../app/src/webkit/java/org/mozilla/focus/webkit/TrackingProtectionWebViewClient.java) ).
+
+We then just need to verify every resource URL to determine whether it can be loaded in that callback: we use a custom trie-based domain matching implementation for
+Focus for Android. This is different from Focus iOS: Focus for iOS was originally a content blocking safari plugin, and used the iOS content blocking API
+( https://developer.apple.com/library/content/documentation/Extensions/Conceptual/ContentBlockingRules/Introduction/Introduction.html ).
+That API uses a specific blocklist format, therefore firefox-ios converts the disconnect lists into the iOS format at build-time.
+
+Focus-ios browser was implemented later, and reuses the iOS content blocking list format (iOS webview was unable to make use of the content blocking API
+at that time, and therefore implemented its own URL matching). This is a regex based format: Focus-ios (browser) therefore stores a list of regexes, and iterates
+over that list to check whether a given resource URL matches. That approach means that focus-ios only needs one copy of the blocklists, however this isn't ideal for performance.
+
+As mentioned, Focus for Android uses a custom Trie implementation instead of iterating over regexes. This does mean that we aren't reusing iOS's blocklist
+format, but it also permits for ~140x faster domain lookup when compared to a port of the iOS domain lookup implementation. The entitylist is similar,
+and we use extended versions of the same Trie for the domain overrides. See [UrlMatcher](../app/src/webkit/java/org/mozilla/focus/webkit/matcher/UrlMatcher.java) for
+the actual matcher implementation.
+
+
+## Miscellaneous notes
+- We do not make any internet connection because every blocklist is built into Firefox Focus. When you enable a blocklsit in settings, our app will load the selected blocklist from disk. We will provide updated lists as an "App update".
+- We discard the site owners and names when parsing the blocklists, that makes it harder to keep track of which trackers have been blocked for a given site. That data would probably have to be added to the blocklist trie if we want a better breakdown of trackers.
+- Our Trie search is recursive, and uses a slightly silly "TrieString" to take care of string reversal and substrings. An iterative implementation would probably be better, and would avoid substrings. Such an implementation would be slightly more complex since we would have to keep track of string indexes, but reduces overhead. Given that current performance is acceptable, there isn't huge value in actually working on this.
diff --git a/mobile/android/focus-android/docs/Crash-Reporting-with-Sentry.md b/mobile/android/focus-android/docs/Crash-Reporting-with-Sentry.md
new file mode 100644
index 0000000000..2e6f5d4f6c
--- /dev/null
+++ b/mobile/android/focus-android/docs/Crash-Reporting-with-Sentry.md
@@ -0,0 +1,147 @@
+# Crash Reporting with Sentry
+
+> **If there is anything in this document that is not clear, is incorrect, or that requires more detail, please file a request through a [Github](https://github.com/mozilla-mobile/focus-android/issues).**
+
+Focus Android uses [Sentry](https://sentry.io) for crash and exception reporting. This kind of reporting gives Mozilla invaluable insight as to why Focus crashes or incorrectly behaves. It is one of the key methods we use to improve the product in terms of stability.
+
+This page explains how Sentry works, how the various parts interact and what kind of data it sends back to Mozilla.
+
+## High-Level Summary
+[Sentry](https://sentry.io) is an open source crash reporting and aggregation platform. Both the client SDK, [github.com/getsentry/sentry-java](https://github.com/getsentry/sentry-java), and the server, [github.com/getsentry/sentry](https://github.com/getsentry/sentry), are open source.
+
+The server is hosted and maintained by Mozilla. There are no third-parties involved, all crash reports to directly from Focus Android to the Sentry server hosted by Mozilla.
+
+On the client side Sentry is invisible. There are no parts to interact with. It reports crashes and fatal errors back to Mozilla in the background. Sentry is enabled when the *Send Usage Data* switch in the *Focus* settings is enabled by the user. By default this switch is enabled in Focus and is an *opt-out* mechanism. In Klar, by default this switch is disabled and is an *opt-in* mechanism.
+
+On the server side there is a dashboard that the Focus team uses to look at incoming crash reports. The dashboard lets us inspect the crash report in detail and for example see where in the application the crash happened, what version of the application was used and what version of Android OS was active. Below is an overview of all the attributes that are part of a crash report.
+
+## Sentry Reports
+
+A typical Sentry crash report contains three categories of data: device, application, crash. It also contains some metadata about the crash report:
+```
+ "id": "6ae18611d6c649529a5eda0e48f42cb4",
+// ...
+ "datetime": "2018-03-30T23:55:03.000000Z",
+// ...
+ "received": 1522454183.0,
+```
+
+To clarify, `id` is a unique identifier for this crash report, *not a unique identifier for the user sending the report.* We explicitly disable the ability to uniquely identify users from their crash reports.
+
+### Device Information
+
+Sentry collects basic information about the device the application is running on. Both static (device type) and dynamic (memory in use, device orientation).
+
+```
+"contexts": {
+ "device": {
+ "screen_resolution": "1920x1080",
+ "battery_level": 100.0,
+ "orientation": "landscape",
+ "family": "AFTN",
+ "model_id": "NS6212",
+ "type": "device",
+ "low_memory": false,
+ "simulator": false,
+ "free_storage": 3967590400,
+ "storage_size": 5735825408,
+ "screen_dpi": 320,
+ "free_memory": 543588352,
+ "memory_size": 1392164864,
+ "online": true,
+ "charging": true,
+ "model": "AFTN",
+ "screen_density": 2.0,
+ "arch": "armeabi-v7a",
+ "brand": "Amazon",
+ "manufacturer": "Amazon"
+ },
+// ...
+ "os": {
+ "rooted": false,
+ "kernel_version": "Linux version 3.14.29 (build@14-use1b-b-42) (gcc version 4.9.2 20140904 (prerelease) (crosstool-NG linaro-1.13.1-4.9-2014.09 - Linaro GCC 4.9-2014.09) ) #1 SMP PREEMPT Fri Jan 19 00:36:45 UTC 2018",
+ "version": "7.1.2",
+ "build": "NS6212",
+ "type": "os",
+ "name": "Android"
+ }
+},
+```
+
+### Application Information
+
+Sentry collects basic information about the Focus app.
+
+```
+ "app": {
+ "app_identifier": "org.mozilla.focus",
+ "app_name": "Focus",
+ "app_start_time": "2018-03-30T16:55:03Z",
+ "app_version": "2.1",
+ "type": "app",
+ "app_build": 11
+// ...
+ "sdk": {
+ "client_ip": "63.245.222.193",
+ "version": "1.7.2-02be9",
+ "name": "sentry-java"
+```
+
+### Crash Information
+
+#### Stack trace
+
+Every crash report contains a *stack trace*, which shows what functions in the Focus code led to this crash. It includes names of Android framework functions and Focus functions. Here's an excerpt of three lines from the stack trace:
+
+```
+ "sentry.interfaces.Exception": {
+ "exc_omitted": null,
+ "values": [
+ {
+ "stacktrace": {
+ "frames": [
+ {
+ "function": "main",
+ "abs_path": "ZygoteInit.java",
+ "module": "com.android.internal.os.ZygoteInit",
+ "in_app": false,
+ "lineno": 801,
+ "filename": "ZygoteInit.java"
+ },
+ {
+ "function": "run",
+ "abs_path": "ZygoteInit.java",
+ "module": "com.android.internal.os.ZygoteInit$MethodAndArgsCaller",
+ "in_app": false,
+ "lineno": 911,
+ "filename": "ZygoteInit.java"
+ },
+ {
+ "function": "invoke",
+ "abs_path": "Method.java",
+ "in_app": false,
+ "module": "java.lang.reflect.Method",
+ "filename": "Method.java"
+},
+```
+
+#### Exception message
+The first line of every stack trace in every crash report contains a *reason* - why did this crash happen. This reason is provided by the developers who wrote the code that decide the app is in an error state. These developers include the Focus team at Mozilla, the Android framework, the Java programming language, and any libraries Mozilla bundles to develop Focus.
+
+Java, the Android framework, and Mozilla are diligent about making sure that no personally identifiable information is put in any of these messages. We keep them technical and to the point. We at Mozilla stay on top of our dependencies to ensure they're not including personally identifiable information as well.
+
+Here's an example message generated by Java:
+```
+java.lang.StringIndexOutOfBoundsException: length=0; regionStart=20; regionLength=20
+```
+
+Example of a Focus generated message:
+```
+java.lang.StringIndexOutOfBoundsException: Cannot create negative-length String
+```
+
+#### Raw data dump
+In the explanations above, some redundant fields and field considered less important were omitted for brevity. To review these omissions, [this is an example of the raw data the server receives](https://gist.github.com/mcomella/50622aef817b40a20714b8550fb19991). This is up-to-date as of March 30, 2018.
+
+## For developers
+For developer documentation such as how to enable Sentry in your builds, see [`SentryWrapper.kt`](https://github.com/mozilla-mobile/focus-android/blob/master/app/src/main/java/org/mozilla/focus/telemetry/SentryWrapper.kt) in the code base.
diff --git a/mobile/android/focus-android/docs/Development-Custom-GeckoView.md b/mobile/android/focus-android/docs/Development-Custom-GeckoView.md
new file mode 100644
index 0000000000..81525b7595
--- /dev/null
+++ b/mobile/android/focus-android/docs/Development-Custom-GeckoView.md
@@ -0,0 +1,90 @@
+# Development with Custom GeckoView
+
+If you are an engineer working on Gecko(View) then you might be interested in building Focus/Klar with your own build of GeckoView.
+
+For this you will need to:
+
+* Checkout mozilla-central (or another branch)
+* Setup your system to build Firefox for Android
+* Package a GeckoView [AAR](https://developer.android.com/studio/projects/android-library.html)
+* Modify your Focus gradle configuration to use your custom GeckoView AAR.
+
+## Setup build system
+
+Follow the [Build instructions](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_for_Android_build) to setup a Firefox for Android build (ARM or x86).
+
+A minimal `mozconfig` for GeckoView development might look like this (ARM):
+
+```
+ac_add_options --enable-application=mobile/android
+ac_add_options --target=arm-linux-androideabi
+# For x86 use: ac_add_options --target=i386-linux-android
+
+ac_add_options --with-android-sdk=""
+ac_add_options --with-android-ndk=""
+```
+
+## Package GeckoView AAR
+
+After setting up your build system you should be able to build and package Firefox for Android:
+
+```bash
+./mach build
+./mach package
+```
+
+Now you can create the GeckoView AAR from the compiled code:
+
+```bash
+./mach android archive-geckoview
+```
+
+This should create a file named `geckoview-*.aar` in your build output folder (MOZ_OBJDIR):
+
+```bash
+$ ls /gradle/build/mobile/android/geckoview/outputs/aar
+geckoview-official-withGeckoBinaries-noMinApi-release.aar
+```
+
+## Point your Focus/Klar build to your AAR
+
+In your Focus/Klar checkout open `app/build.gradle` (__not__ the build.gradle file in the root directory!) and locate the `repositories` block. This block defines where gradle will look for dependencies. Add the absolute path to your AAR as follows:
+
+```groovy
+repositories {
+ // ...
+
+ flatDir(
+ name: 'localBuild',
+ dirs: ''
+ )
+}
+```
+
+Now locate the ```dependencies``` block. This block defines which dependencies are needed to compile the application. Locate the already existing `armImplementation` and `x86Implementation` statements. Those are currently pointing to AARs that are pulled from our build servers. Replace the correct one (x86 / ARM) to use the name of your local AAR:
+
+```groovy
+dependencies {
+ // ...
+
+ // armImplementation "org.mozilla:geckoview-nightly-armeabi-v7a:60.0a1"
+ armImplementation (
+ name: 'geckoview-official-withGeckoBinaries-noMinApi-release',
+ ext: 'aar'
+ )
+ x86Implementation "org.mozilla:geckoview-nightly-x86:60.0a1"
+
+ // ...
+}
+```
+
+Now either build the `klarArmDebug` or `klarX86Debug` build variant from Android Studio (Running "Sync Project with Gradle files" once might be needed) or build and install from the command line:
+
+```bash
+./gradlew installKlarArmDebug
+./gradlew installKlarX86Debug
+```
+
+Finally, the default renderer might be set as Webview. You can check for the presence or absence of the Gecko logo at `focus:about` to verify. You may change rendering engine settings at `focus:test` and then press back for the app to restart. This should store your engine preference until you uninstall or clear data.
+
+You can also change the default engine at `focus-android/app/src/debug/java/org/mozilla/focus/web/Config.kt`. Setting `DEFAULT_NEW_RENDERER` to `true` will use GeckoView.
diff --git a/mobile/android/focus-android/docs/Feature-&-Issue-workflow.md b/mobile/android/focus-android/docs/Feature-&-Issue-workflow.md
new file mode 100644
index 0000000000..02c43f2ed7
--- /dev/null
+++ b/mobile/android/focus-android/docs/Feature-&-Issue-workflow.md
@@ -0,0 +1,54 @@
+# Feature & Issue workflow
+
+## High-Level Steps
+
+| Issue opened| ➡️ |Triaged| ➡️ |Ready in/for Backlog| ➡️ | Ready for UX | ➡️ | Ready for Eng| ➡️ |Eng done| ➡️ |Ready for review| ➡️ |Ready for QA| ➡️ |Done
+|------|------|------|------|------|------|------|------|------|------|------|------|------|------|------|------|------
+
+## Details
+
+**Issue opened**
+- Issue is created (by anybody)
+- Ready for triage, where the issue gets either closed, commented on, left in triage for more info, assigned labels, and milestones
+
+**Triaged**
+- Is triaged, which means issue has a P label, has a milestone assigned (can be backlog)
+- Issue if possible can already get estimation, T-shirt size
+
+**Ready in/for Backlog**
+- Issue is in Backlog milestone
+- Issue includes enough information (follows template) that allows team members to estimate, understand the scope, user benefit, the what, and acceptance criteria
+- Request for probes/KPIs reviewed and approved by product and data analyst
+- Once estimated, it's ready to be moved into a sprint
+
+**Ready for UX**
+- When UX picks up issue, assigns themselves to the issue
+- UX to provide mocks, attach to Github issue
+- Once UX is done, ready for eng, UX resource to unassign themselves, and use "ready for eng breakdown" label
+- Can be skipped if no UX is needed
+
+**Ready for Eng**
+- Eng should only pick up issues that are ready for Eng and assigned to a sprint/milestone (not Backlog)
+- Copy/content strategist has provided strings
+
+**Eng done**
+- Issue is eng done only if PR was submitted AND PR was reviewed
+- PR is closed but not issue
+- Issue will be assigned by eng to product manager (PM assigns it to other stakeholders where appropriate)
+- Strings exported
+
+**Ready for Review**
+- During sprints: Once PR is closed, the next day Nightly should have the changes and stakeholders can verify
+- During sprint review: everyone can review, comment
+- Once stakeholders(Marketing, PM) are happy with result, PM assigns "ready for QA" label
+
+**Ready for QA**
+- QA to watch for issues with "ready for QA" label AND in current sprint or major milestone
+- QA to assign the ticket they are currently working on to themselves
+- Once QA verified and completed issue, un-assign themselves from issue
+- Outline and record test steps in TestRail
+- Identify new test(s) for automation and create github issue with "automation" label
+- Closes issue
+
+**Done**
+- Issue is closed, no assignees and assigned sprint or major milestone
diff --git a/mobile/android/focus-android/docs/GeckoView-(In-Progress).md b/mobile/android/focus-android/docs/GeckoView-(In-Progress).md
new file mode 100644
index 0000000000..1691579ab1
--- /dev/null
+++ b/mobile/android/focus-android/docs/GeckoView-(In-Progress).md
@@ -0,0 +1,6 @@
+# Running GeckoView in a debug environment
+
+Currently there are two ways to use GeckoView in development.
+
+1. You can navigate to `focus:test` and flip the toggle to switch to the new engine
+2. In [`Config.kt`](https://github.com/mozilla-mobile/focus-android/blob/master/app/src/debug/java/org/mozilla/focus/web/Config.kt) you can set `DEFAULT_NEW_RENDERER` to `true`.
diff --git a/mobile/android/focus-android/docs/Home.md b/mobile/android/focus-android/docs/Home.md
new file mode 100644
index 0000000000..b5daab333f
--- /dev/null
+++ b/mobile/android/focus-android/docs/Home.md
@@ -0,0 +1,40 @@
+# Firefox Focus for Android
+
+> Browse like no one’s watching. The new Firefox Focus automatically blocks a wide range of ads and online trackers — from the moment you launch it to the second you leave it. Easily erase your history, passwords and cookies, so you won’t get followed by things like unwanted ads.
+
+https://www.mozilla.org/firefox/focus/
+
+*Don't see what you're looking for? Check out our shared docs: https://github.com/mozilla-mobile/firefox-android/tree/main/docs/shared.*
+
+## User support
+
+* Support articles: https://support.mozilla.org/en-US/products/focus-firefox/firefox-focus-android
+* Support forum: https://support.mozilla.org/en-US/questions/focus-firefox?show=all
+
+## Download
+
+* Google Play - Firefox Focus: https://play.google.com/store/apps/details?id=org.mozilla.focus
+* Google Play - Firefox Klar: https://play.google.com/store/apps/details?id=org.mozilla.klar
+* APKs: https://github.com/mozilla-mobile/firefox-android/releases
+
+## Contribute
+
+* [Contributing (Writing code, translating the app, testing the app)](https://github.com/mozilla-mobile/firefox-android/blob/main/docs/shared/android/CONTRIBUTING.md)
+* [List of issues](https://bugzilla.mozilla.org/describecomponents.cgi?product=Focus)
+* [Mozilla Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/)
+
+## Communication
+* Send an email to our [public Firefox Focus mailing list](https://mail.mozilla.org/listinfo/firefox-focus-public): firefox-focus-public@mozilla.com
+* Send an email to our internal Firefox Focus mailing list (mozilla.com email required): firefox-focus@mozilla.com
+
+## Developer Documentation
+
+* [Architecture decisions](https://github.com/mozilla-mobile/firefox-android/blob/main/focus-android/docs/Architecture-Decisions.md)
+* [Automation](https://github.com/mozilla-mobile/firefox-android/blob/main/docs/shared/android/automation.md)
+* [Content blocking](https://github.com/mozilla-mobile/firefox-android/blob/main/focus-android/docs/Content-blocking.md)
+* [Multisession architecture](https://github.com/mozilla-mobile/firefox-android/blob/main/focus-android/docs/Multisession-architecture.md)
+* [Localization](https://github.com/mozilla-mobile/focus-android/wiki/Localization)
+* [Telemetry](https://github.com/mozilla-mobile/firefox-android/blob/main/focus-android/docs/Telemetry.md)
+
+## Product Management
+* [Release schedule 2018](https://wiki.mozilla.org/Mobile/Focus/Android/Train_Schedule)
diff --git a/mobile/android/focus-android/docs/Homescreen-Tips.md b/mobile/android/focus-android/docs/Homescreen-Tips.md
new file mode 100644
index 0000000000..ce4ebb4ec0
--- /dev/null
+++ b/mobile/android/focus-android/docs/Homescreen-Tips.md
@@ -0,0 +1,35 @@
+# Homescreen Tips
+
+## Homescreen Tips Explained 💡
+
+Homescreen tips launched in Firefox Focus 7.0 in October 2018. They allow users to get more information about the product without having to research on their own or dig around in settings. Tips also enable users to _quickly_ access portions of the settings menu they may not know how to navigate otherwise. All tips have either a deep link to settings or a link to their corresponding SUMO article (if available).
+
+
+
+## How Tips are Displayed
+Currently, tips are shown based on the following constraints:
+1. We will never show a user a tip from a feature they have _already engaged with_
+2. Tips are shown in a random order, and all have an equal chance of being shown
+3. Only three tips are displayed per session before we revert back to showing "Browse. Erase. Repeat."
+4. Tips are displayed on the home screen after launch, and are refreshed after each erase.
+
+Some notes to keep in mind:
+* clicking a SUMO link does not count as engaging with the feature
+* the "disable tips" tip is shown less frequently and is _guaranteed_ to show on the third launch of the app.
+* a "session" is defined as every time the app is loaded into memory (i.e. has not been force quit).
+
+## List of Tips
+Below is a list of tips available with their corresponding deep links:
+
+* "Open every link in Firefox Focus. Set Firefox Focus as default browser" -> Settings deep link
+* "Turn off tips on the Firefox Focus start screen" -> Settings deep link
+* "Turn off tips on the start screen" -> Settings deep link
+* "Trusted site? Allowlist disables Tracking Protection for sites you know and trust." -> Settings deep link
+* "Site behaving unexpectedly? Try turning off Tracking Protection" -> None
+* "Get one-tap access to sites you use most. Menu > Add to Home screen" -> [SUMO article](https://support.mozilla.org/en-US/kb/add-web-page-shortcuts-your-home-screen)
+* "Autocomplete URLs for sites you use most. Long-press any URL in the address bar" -> [SUMO article](https://support.mozilla.org/en-US/kb/autocomplete-settings-firefox-focus-address-bar)
+* "Open a link in a new tab. Long-press any link on a page" -> [SUMO article](https://support.mozilla.org/en-US/kb/open-new-tab-firefox-focus-android)
+* "Rather see the full desktop site? Menu > Request desktop site" -> [SUMO article](https://support.mozilla.org/en-US/kb/switch-desktop-view-firefox-focus-android)
+
+## The Future of Tips
+If tips are received well, there are plans to update them in the future. A major change in consideration is hosting them server-side so they can be updated on the fly, rather than having to push an update to the app in order to change them. An android component for this feature is also being investigated.
diff --git a/mobile/android/focus-android/docs/Multisession-architecture.md b/mobile/android/focus-android/docs/Multisession-architecture.md
new file mode 100644
index 0000000000..465f4249ac
--- /dev/null
+++ b/mobile/android/focus-android/docs/Multisession-architecture.md
@@ -0,0 +1,19 @@
+# Multisession Architecture
+
+To support multiple simultaneous browsing sessions the architecture of Focus has been refactored to strictly separate sessions (and associated data) from the UI that displays this data.
+
+Sessions are managed by a global `SessionManager` instance. UI components display data hold by `Session` objects.
+
+The new multisession architecture makes use of Android's [Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html). The list of sessions in the `SessionManager` and the data inside a `Session` is returned as [LiveData](https://developer.android.com/topic/libraries/architecture/livedata.html) objects. UI components can observe those LiveData objects for changes and update the UI state if needed.
+
+![](http://i.imgur.com/CUb5ZLW.png)
+
+* _BrowserFragment_ is the fragment that displays the browser chrome and web content. A _BrowserFragment_ is always connected to a _Session_ (1:1).
+
+* _IWebView_ is a generic interface for the view that renders web content. At runtime it's implemented by either [WebView](https://developer.android.com/reference/android/webkit/WebView.html) or GeckoView. State changes will be reported to a class that implements _IWebView.Callback_.
+
+* With the multisession architecture _IWebView.Callback_ is implemented by _SessionCallbackProxy_. The purpose of this class is to update the _Session_ object for this browsing session. Currently _SessionCallbackProxy_ will still delegate some callback methods to the BrowserFragment. This is expected to be removed in a follow-up refactoring eventually.
+
+* _BrowserFragment_ displays data hold by the _Session_ object. Data that changes periodically (e.g. URL, progress, ..) are represented as [LiveData](https://developer.android.com/topic/libraries/architecture/livedata.html) objects and can be observed so that the UI can be updated whenever the state changes.
+
+* Sessions are switched by displaying a new _BrowserFragment_ for a different _Session_ object.
diff --git a/mobile/android/focus-android/docs/Recommended-pre-push-hook.md b/mobile/android/focus-android/docs/Recommended-pre-push-hook.md
new file mode 100644
index 0000000000..a9d377205c
--- /dev/null
+++ b/mobile/android/focus-android/docs/Recommended-pre-push-hook.md
@@ -0,0 +1,30 @@
+# Recommended pre-push hook
+
+If you want to reduce your PR turn-around time, I'd recommend adding a
+pre-push hook: this script will stop a push if the unit tests or linters
+fail, finding the failures before it hits TaskCluster (which takes
+forever to dig through the logs):
+```sh
+#!/bin/sh
+
+./gradlew -q \
+ checkstyle \
+ ktlint \
+ pmd \
+ detektCheck \
+ app:assembleFocusArmDebug
+
+
+# Tasks omitted because they take a long time to run:
+# - unit test on all variants
+# - UI tests
+# - lint (compiles all variants)
+```
+
+To use it:
+1. Create a file with these ^ contents (exclude the "\`") at `/.git/hooks/pre-push`
+1. Make it executable: `chmod 755 /.git/hooks/pre-push`
+
+And it will run before pushes. Notes:
+- Run `git push ... --no-verify` to push without making the check
+- It takes ~30 seconds to run. If you think this hook takes too long, you can remove the unit test line and it becomes almost instant.
diff --git a/mobile/android/focus-android/docs/Release-Process.md b/mobile/android/focus-android/docs/Release-Process.md
new file mode 100644
index 0000000000..e39789ba31
--- /dev/null
+++ b/mobile/android/focus-android/docs/Release-Process.md
@@ -0,0 +1,58 @@
+# Release Process
+
+## Creating a new Release Branch
+
+1. Create a branch name with the format `releases_v[version]` (for example: `releases_v87.0`).
+2. Pin the Android Components version to the final release version with the format `[version].0.0`.
+
+ For example:
+
+ | FILE | CURRENT VERSION | RELEASE VERSION |
+ |--------------------|--------------------|-----------------|
+ | AndroidComponents | 73.0.20210223143117| 73.0.0 |
+
+ For reference, the AC release checklist can be found at https://mozac.org/contributing/release-checklist.
+ Instead of updating the AC version manually, you could also wait for automation to update the [ac version](https://github.com/mozilla-mobile/focus-android/actions/workflows/update-ac.yml).
+
+3. In the GitHub UI, create a branch on the upstream with the format `releases_v[version]`.
+4. Push the `releases_v[version]` branch as a PR with the commit message "Set version to [version]" and commit into the upstream `releases_v[version]` branch.
+6. Create a new [milestone](https://github.com/mozilla-mobile/focus-android/milestones) for the next release and close the existing milestone.
+7. Announce the new `releases_v[version]` branch on Slack in #releaseduty-mobile.
+
+Automation should take over after you land your PR into the upstream `releases_v[version]` branch. You can verify by clicking on the branch in the UI, and looking for the green/yellow dot that will list links to the running build tasks.
+
+## After the Beta release
+
+ Update the `versionName` from `build.gradle(Module:focus-android.app)` to the next Focus beta release version.This should be the `[version + 1]`.
+ For example, if the release cut was for `90`, we're looking to prepare the latest nightly for `91`.
+ For example:
+
+ | FILE | CURRENT VERSION | RELEASE VERSION |
+ |--------------------|--------------------|-----------------|
+ | build.gradle | 90 | 91 |
+
+## Renew telemetry
+
+After the Beta cut, another task is to renew/remove all soon to expire telemetry probes. What we're looking for is to create a list of telemetry that will expire in `[release_version add 2]`. See [Firefox Release Calendar](https://wiki.mozilla.org/Release_Management/Calendar) for the current Release version. There is a script that will help with finding these soon to expire telemetry.
+
+1. Use the helper `python3 tools/data_renewal_generate.py release_version+2` to detected and generate files that will help create the following files:
+ - `[release_version add 2]`_expiry_list.csv
+ - `[release_version add 2]`_renewal_request.txt
+2. Upload the `[release_version add 2]`_expiry_list.csv to Google sheet in this [shared Google Drive](https://drive.google.com/drive/folders/1_ertMvn59eE9JmN721RqOjW6nNtxq9oS?usp=sharing) and contact product to review. For each telemetry listed answer decide for:
+ - Renew the metric (for how long? Add 12 versions?)
+ - Choose not to renew (but not delete)
+ - Choose to remove the metric
+ - Renew the metric and set to never expire (this should only be for business critical metrics)
+3. Note that `metrics.yaml` is also modified. Once the review is over, continue to modify `metrics.yaml` to match the decision made in the Google sheet. Make sure to add the PR link and if the telemetry never expires, add the email of the owner as contact.
+4. Create a PR for review. Modify `[release_version add 2]`_renewal_request.txt and paste it to the PR for data review. Make sure to add the answers for:
+ - When will this collection now expire?
+ - Why was the initial period of collection insufficient?
+
+
+## Ask for Help
+
+If you run into any problems, please ask any questions on Slack in #releaseduty-mobile.
+
+References:
+- https://github.com/mozilla-mobile/focus-android/tree/releases_v96.0
+- https://github.com/mozilla-mobile/focus-android/pull/6012
diff --git a/mobile/android/focus-android/docs/Release-tracks.md b/mobile/android/focus-android/docs/Release-tracks.md
new file mode 100644
index 0000000000..ad0c147901
--- /dev/null
+++ b/mobile/android/focus-android/docs/Release-tracks.md
@@ -0,0 +1,53 @@
+# Release tracks
+
+![](https://raw.githubusercontent.com/mozilla-mobile/focus-android/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
+
+We have three public release tracks for Focus/Klar:
+
+* **Production**: That's the version everyone sees on Google Play. We rollout releases gradually in stages.
+
+* **Beta**: This track is for Beta builds. We use this track to test builds with a wider audience and to uncover problems/crashes that may prevent us from releasing the build. We may push multiple releases to this track until we feel positive about publishing to the *production* track.
+
+* **Nightly**: We publish automated builds on this track every 24 hours. Those builds reflect the latest state of the repository (master). This track allows users to experience the newest Firefox Focus/Klar innovations in an unstable environment and provide feedback on features and performance to help determine what makes the final release. Nightly builds can be unstable and contain incomplete or defect features.
+
+
+## Nightly
+
+![](https://raw.githubusercontent.com/mozilla-mobile/focus-android/master/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png)
+
+* Updates every 24 hours.
+* Latest development state
+* Can be unstable and contain incomplete or defect features
+
+### How to sign up for Nightly
+
+To get Focus Nightly on your device, follow these steps:
+
+1) Visit https://groups.google.com/g/firefox-focus-pre-release and join the Google Group
+2) After you have joined the group opt-in to receive Nightly builds, again with the same Google account: https://play.google.com/apps/testing/org.mozilla.focus.nightly
+3) Download Firefox Focus (Nightly) from Google Play: https://play.google.com/store/apps/details?id=org.mozilla.focus.nightly
+
+Make sure you use the same Google Account for both steps.
+
+
+## Beta
+
+
+![](https://raw.githubusercontent.com/mozilla-mobile/focus-android/master/app/src/focusBeta/res/mipmap-xxxhdpi/ic_launcher.png)
+* Updates to the Beta track will typically be 1-2 times weekly.
+
+### How to sign up for beta
+To get Focus Beta on your device, follow these steps:
+1) Visit https://groups.google.com/g/firefox-focus-pre-release and join the Google Group
+2) After you have joined the group opt-in to receive Beta builds, again with the same Google account: https://play.google.com/apps/testing/org.mozilla.focus.beta
+3) Download Firefox Focus (Beta) from Google Play: https://play.google.com/store/apps/details?id=org.mozilla.focus.beta
+
+Make sure you use the same Google Account for both steps.
+
+
+### Manual downloads
+
+The latest Beta/Release builds can be downloaded manually from:
+https://github.com/mozilla-mobile/focus-android/releases
+
+Note that manually downloaded builds **do not update automatically**.
diff --git a/mobile/android/focus-android/docs/Removing-strings.md b/mobile/android/focus-android/docs/Removing-strings.md
new file mode 100644
index 0000000000..02a3cd86c5
--- /dev/null
+++ b/mobile/android/focus-android/docs/Removing-strings.md
@@ -0,0 +1,29 @@
+# Removing strings
+
+## Marking an unused string to be removed
+
+Removing strings manually could cause crashes in **Beta** and **Release** versions 💥 , as the removed strings could be uplifted to release branches by mistake, where strings are still needed. For this reason, we need a special process to remove them.
+
+Any landed string that is not removed while in development in Nightly will persist through 3 Firefox releases (Nightly, Beta, Release) before we can remove them from our code base. For example,
+if you want to remove a string that has already shipped prior to **Firefox Nightly 93**, the same string will still be in-use in **Firefox Beta 92** and **Firefox Release 91**. This means the string will be marked as unused and removed in 93 while still riding the train, and it can be removed safely when **Firefox Release 93** no longer ships, for instance, **Firefox Release 94** and beyond.
+
+To keep us safe when you want to remove strings from nightly:
+
+1. Add these attribute to the target strings `moz:removedIn="<>"` and `tools:ignore="UnusedResources"`.
+
+```xml
+ Close
+```
+Example commit https://github.com/mozilla-mobile/focus-android/pull/6291/files
+
+## When to remove an unused string and how
+
+Strings that have been tagged with `moz:removedIn` attributes are safe to be removed after the marked version is no longer shipping and no longer in-use or needed.
+
+Consult the [Firefox release calendar](https://wiki.mozilla.org/Release_Management/Calendar). Let's say the Beta cut just happened and we are at Firefox Nightly 109, Firefox Beta 108 and Firefox Release 107. Everything marked with `moz:removedIn` <= 106 can now be removed.
+
+You only need to remove the en-US strings within [values/strings.xml](https://github.com/mozilla-mobile/focus-android/blob/main/app/src/main/res/values/strings.xml), and this change will propagate to the other locales.
+
+## Future
+
+It would be nice to add some automatization to delete the strings that have the `moz:removedIn` attributes where a full cycle has happen (3 releases versions from the actual release version).
diff --git a/mobile/android/focus-android/docs/Sprint-Process.md b/mobile/android/focus-android/docs/Sprint-Process.md
new file mode 100644
index 0000000000..7f51230fe4
--- /dev/null
+++ b/mobile/android/focus-android/docs/Sprint-Process.md
@@ -0,0 +1,47 @@
+# Sprint Process
+
+Focus follows a 2-week sprint cycle with 6-week milestone releases. Dot-releases with bugfixes are released every two weeks. Our upcoming train schedule is [here](https://wiki.mozilla.org/Mobile/Focus/Android/Train_Schedule).
+
+## Issue naming and labels
+
+### Labels
+Priority labels are based on the [Bugzilla triage process][triage priority] and set during triage to determine when they'll be worked on:
+* `P1`: Issues for the current 2-week sprint.
+ * [Open engineering issues](https://github.com/mozilla-mobile/focus-android/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AP1%20NOT%20%5Bux%5D%20in%3Atitle%20)
+ * [Open UX issues](https://github.com/mozilla-mobile/focus-android/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AP1%20ux%20in%3Atitle%20)
+* `P2`: Issues for the 6-week milestone release.
+ * [Open engineering issues](https://github.com/mozilla-mobile/focus-android/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AP2%20NOT%20%5Bux%5D%20in%3Atitle%20)
+ * [Open UX issues](https://github.com/mozilla-mobile/focus-android/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AP2%20ux%20in%3Atitle%20)
+* `P3`: Backlog
+* `P5`: Will not fix but will accept a patch
+
+Other labels:
+* `addressed`: Label for excluding items from triage. Should be used for [meta] items.
+
+### Issue Prefixes
+* `[meta]`: larger issues that need to be broken down, into a `[breakdown]` issue and issues for its smaller parts. This should include a checklist of all the issues (including the `[breakdown]` issue).
+
+ These do NOT get a P* label, but should be in a milestone.
+* `[breakdown]`: issue to track the work of breaking down a larger bug
+
+## Triage - Weekly
+- ([Link](https://github.com/mozilla-mobile/focus-android/issues?q=is%3Aissue+is%3Aopen+-label%3AP1+-label%3AP2+-label%3AP3+-label%3AP4+-label%3AP5+-label%3Aaddressed+-label%3Ablocked+sort%3Aupdated-desc+no%3Amilestone)) Assign priority labels (P1, P2, ...) to bugs without priority labels or the `addressed` label
+- ([Link](https://github.com/mozilla-mobile/focus-android/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3AP1+no%3Amilestone+)) Ensure all P1 labels are assigned a milestone
+- ([Link](https://github.com/mozilla-mobile/focus-android/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3AP2+no%3Amilestone+)) Ensure all P2 labels are assigned a milestone
+
+## Weekly Bug management (alternating)
+
+### Sprint planning
+Deciding what goes into a sprint, promote P2 issues to P1. After every release, this meeting is for deciding what goes into the upcoming milestone - add issues to the milestone and set them as P2.
+
+### Backlog grooming
+Handling Triage overflow, adding to the contributor bug lists, looking at milestone lists.
+
+[triage priority]: https://wiki.mozilla.org/Bugmasters/Process/Triage#Weekly_or_More_Frequently_.28depending_on_the_component.29
+## Monthly Roadmap Planning
+Plan and prioritize features for upcoming milestones - an update will be emailed out.
+
+Plan and facilitate workweeks
+release schedule
+trying to work with things without proper training
+too much observing
diff --git a/mobile/android/focus-android/docs/Telemetry.md b/mobile/android/focus-android/docs/Telemetry.md
new file mode 100644
index 0000000000..a6d202a93e
--- /dev/null
+++ b/mobile/android/focus-android/docs/Telemetry.md
@@ -0,0 +1,345 @@
+# Telemetry
+For clients that have "send anonymous usage data" enabled Focus sends a "core" ping and an "event" ping to Mozilla's telemetry service. Sending telemetry can be disabled in the app's settings. Builds of "Focus for Android" have telemetry enabled by default ("opt-out") while builds of "Klar for Android" have telemetry disabled by default.
+
+## Core ping
+
+Focus for Android creates and tries to send a "core" ping whenever the app goes to the background. This core ping uses the same format as Firefox for Android and is [documented on firefox-source-docs.mozilla.org](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/core-ping.html).
+
+## Event ping
+
+In addition to the core ping an event ping for UI telemetry is generated and sent as soon as the app is sent to the background.
+
+### Settings
+
+As part of the event ping the most recent state of the user's setting is sent (default values in **bold**):
+
+| Setting | Key | Value(*)
+|--------------------------|---------------------------------|----------------------
+| Default search engine | pref_search_engine | **bundled engine name**/`custom`(**)
+| Block ad trackers | pref_privacy_block_ads | **true**/false
+| Block analytics trackers | pref_privacy_block_analytics | **true**/false
+| Block social trackers | pref_privacy_block_social | **true**/false
+| Block content trackers | pref_privacy_block_other | true/**false**
+| Block web fonts | pref_performance_block_webfonts | true/**false**
+| Block images | pref_performance_block_images | true/**false**
+| Locale override | pref_locale | **empty string**/`locale-code`(***)
+| Default browser | pref_default_browser | true/**false**
+| Stealth mode | pref_secure | **true**/false
+| Autocomplete (Default) | pref_autocomplete_preinstalled | true/false
+| Autocomplete (Custom) | pref_autocomplete_custom | true/false
+| Show tips on Firefox Focus home screen | pref_key_tips | **true**/false
+
+(*) All values are sent as a `String`.
+
+(**) If the default is one of the bundled engines, the name of the engine. Otherwise, just `custom`.
+
+(***) An **empty string** value indicates "System Default" locale is selected.
+
+### Events
+
+The event ping contains a list of events ([see event format on readthedocs.io](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/events.html)) for the following actions:
+
+#### Sessions
+
+| Event | category | method | object | value |
+|------------------------------------------|----------|------------|--------|--------|
+| Start session (App is in the foreground) | action | foreground | app | |
+| Stop session (App is in the background) | action | background | app | |
+
+#### Browsing
+
+| Event | category | method | object | value | extras. |
+|----------------------------------------|----------|-----------------------|------------|--------|------------|
+| URL entered | action | type_url | search_bar | | |
+| Search entered | action | type_query | search_bar | | |
+| Search hint clicked ("Search for ..") | action | select_query | search_bar | | |
+| Link from third-party app | action | intent_url | app | | |
+| Text selection action from app | action | text_selection_intent | app | | |
+| Long press on image / link | action | long_press | browser | | |
+| "Pull to refresh" | action | swipe | browser | reload | |
+| Autofill popup is shown | action | show | autofill | | |
+| Autofill performed | action | click | autofill | | |
+| Enable Search Suggestion from prompt | action | click | search_suggestion_prompt | true/false | |
+
+#### Custom Tabs
+
+| Event | category | method | object | value | extra |
+|---------------------------|----------|-------------------|----------------------|------------|------------|
+| Custom tab opened | action | intent_custom_tab | app | | `options`* |
+| "Close" button clicked | action | click | custom_tab_close_bu | | `tabs`* |
+| "Action" button clicked | action | click | custom_tab_action_bu | | |
+| Custom menu item selected | action | open | menu | custom_tab | |
+
+(*) `options` is a JSON map of enabled custom tab options as requested by the third-party app, e.g. [hasToolbarColor, hasCloseButton]
+
+(*) `tabs` is a JSON map containing the position of the currently selected tab and the total number of open tabs:
+
+```JavaScript
+{
+ "selected": "2" // Currently selected tab (Zero-based numbering; -1 if no tab is selected)
+ "total": "5" // Total number of open tabs
+}
+```
+
+#### Context Menu
+
+| Event | category | method | object | value |
+|----------------------------------------|----------|--------|---------------------|------------|
+| Link context menu dismissed | action | cancel | browser_contextmenu | link |
+| Share Link menu item selected | action | share | browser_contextmenu | link |
+| Copy link menu item selected | action | copy | browser_contextmenu | link |
+| Image context menu dismissed | action | cancel | browser_contextmenu | image |
+| Share Image menu item selected | action | share | browser_contextmenu | image |
+| Copy Image menu item selected | action | copy | browser_contextmenu | image |
+| Save Image menu item selected | action | save | browser_contextmenu | image |
+| Image with Link context menu dismissed | action | cancel | browser_contextmenu | image+link |
+| Open link in new tab | action | open | browser_contextmenu | tab |
+| Open new tab in Focus from customtab context menu | action | open | browser_contextmenu | full_browser |
+
+
+
+#### Erasing session
+
+| Event | category | method | object | value | extras |
+|----------------------------------------|----------|-------------|---------------------|------------|---------|
+| Floating action button clicked | action | click | erase_button | | `tabs`* |
+| Back button clicked: Home screen | action | click | back_button | erase_home | `tabs`* |
+| Back button clicked: Previous app | action | click | back_button | erase_app | `tabs`* |
+| Notification clicked | action | click | notification | erase | `tabs`* |
+| Notification "Erase and Open" click | action | click | notification_action | erase_open | `tabs`* |
+| Home screen shortcut clicked | action | click | shortcut | erase | `tabs`* |
+| Erase history in tabs tray | action | click | tabs_tray | erase | `tabs`* |
+| App removed by user from "recent apps" | action | click | recent_apps | erase | `tabs`* |
+
+(*) `tabs` is a JSON map containing the position of the currently selected tab and the total number of open tabs:
+
+```JavaScript
+{
+ "selected": "2" // Currently selected tab (Zero-based numbering; -1 if no tab is selected)
+ "total": "5" // Total number of open tabs
+}
+```
+
+#### Menu
+
+| Event | category | method | object | value | extras |
+|---------------------------------------------|----------|----------|-----------------|--------------|----------|
+| Enable/Disable content blocking for session | action | click | blocking_switch | true/false | |
+| Share URL with third-party app | action | share | menu | | |
+| Open default app for URL | action | open | menu | default | |
+| Open Firefox | action | open | menu | firefox | |
+| Open with ... (Show app chooser) | action | open | menu | selection | |
+| Open full browser from custom tab | action | click | menu | full_browser | |
+| "Download Firefox" clicked (in app chooser) | action | install | app | firefox | |
+| "What's new" | action | click | menu | whats_new | `state`* |
+| Reload current page | action | click | menu | reload | |
+| Report site issue | action | click | menu | report_issue | |
+| Find in Page | action | click | menu | find_in_page | |
+| Request desktop site | action | click | desktop_request_check| true/false | |
+
+(*) `state` is a JSON map containing information about whether the menu item was highlighted:
+
+```JavaScript
+{
+ "highlighted": "true" // Whether the menu item was highlighted or not
+}
+```
+
+#### Notification
+
+| Event | category | method | object | value | extras |
+|---------------------------------------|----------|----------|---------------------|------------|---------|
+| Notification clicked (Erase) | action | click | notification | erase | |
+| Action "Open" clicked | action | click | notification_action | open | |
+| Action "Erase and Open" clicked | action | click | notification_action | erase_open | `tabs`* |
+
+(*) `tabs` is a JSON map containing the position of the currently selected tab and the total number of open tabs:
+
+```JavaScript
+{
+ "selected": "2" // Currently selected tab (Zero-based numbering; -1 if no tab is selected)
+ "total": "5" // Total number of open tabs
+}
+```
+#### Page Load Time Histogram
+
+
+| Event | category | method | object | extras|
+|-----------------|----------|--------------|---------| ----- |
+| Histogram for Page Load Times for Foreground Session | histogram | foreground | browser | histogram*|
+
+(*) There are 200 extras attached to this event, each a bucket of 100 ms each, with the key as the minimum value in the bucket and the value as the corresponding number of events in the bucket. Anything over 20,000 is put in the last bucket. For example:
+```
+{”0":"2"}
+{“100”:"3"}
+...
+{"19900", "4"}
+```
+
+#### URI Count
+
+| Event | category | method | object | extra |
+|---------------------------------------------|----------|----------|---------------------|-----------------|
+| The count of the total non-unique http(s) URIs visited in a subsession, including page reloads, after the session has been restored. This does not include background page requests and URIs from embedded pages or private browsing | action | open | browser | `{"total_uri_count": num }` |
+| The count of the unique domains visited in a subsession, after the session has been restored. Subdomains under eTLD are aggregated after the first level (i.e. test.example.com and other.example.com are only counted once). This does not include background page requests and domains from embedded pages or private browsing. | action | open | browser | `{"unique_domains_count": num }` |
+
+
+#### Downloads
+
+| Event | category | method | object | value |
+|---------------------------------------------|----------|----------|---------------------|-----------------|
+| Clicked "Download" | action | click | download_dialog | download |
+| Clicked "Cancel" | action | click | download_dialog | cancel |
+
+#### Open Focus From Icon
+
+| Event | category | method | object | value |
+|---------------------------------------------|----------|----------|---------------------|-----------------|
+| Launched Focus From Icon | action | click | app_icon | open |
+| Resume Focus From Icon | action | click | app_icon | resume |
+
+#### Add to Home screen
+
+| Event | category | method | object | value |
+|-----------------------------------------|----------|----------|--------------------------|-------------------|
+| Clicked "Add to Home screen" in dialog | action | click | add_to_homescreen_dialog | add_to_homescreen |
+| Clicked "Cancel" in dialog | action | click | add_to_homescreen_dialog | cancel |
+| Open Focus from Home screen shortcut | action | click | homescreen_shortcut | open |
+
+#### Share to Focus Event
+
+| Event | category | method | object | value |
+|---------------------------------------------|----------|----------|---------------------|-----------------|
+| Shared URL to Focus | action | share_intent | app | url |
+| Shared Search Terms to Focus | action | share_intent | app | search |
+
+#### Settings
+
+| Event | category | method | object | value | extras |
+|--------------------------------|----------|----------|-------------------------|-------|--------------------------
+| Setting changed | action | change | setting | | `{ "to": }`
+| Autocomplete domain added | action | save | autocomplete_domain | | `{ "source": }`
+| Autocomplete domain removed | action | remove | autocomplete_domain | | `{ "total": 5 }`
+| Autocomplete domain reordered | action | reorder | autocomplete_domain | | `options*`
+| Open Exceptions Setting | action | open | allowlist | |
+| Remove Exceptions Domains | action | remove | allowlist | |`{ "total": 5 }`
+| Remove All Exceptions Domains | action | remove_all |allowlist | |
+| Default search engine clicked | action | open | search_engine_setting | |
+| Change default search engine | action | save | search_engine_setting | | `{"source": src* }`
+| Select "Remove" engines screen | action | remove | search_engine_setting | |
+| Delete search engines | remove | remove | remove_search_engines | |`{"selected": num* }`
+| Restore bundled engines | action | restore | search_engine_setting | |
+| Select "Add another engine" | action | show | custom_search_engine | |
+| Save custom search engine | action | save | custom_search_engine | | `{"success": bool* }`
+| Click "Add search engine" ℹ️ | action | click | search_engine_learn_more| |
+| Open with default browser prompt| action | show | make_default_browser_open_with| |
+| Settings default browser prompt| action | show | make_default_browser_settings| |
+
+
+(*) `options` is a JSON map containing:
+
+```JavaScript
+{
+ "from": 5
+ "to": 2
+}
+```
+
+(*) `src` can be either `bundled` or `custom`
+
+(*) `num` number of engines selected for deletion
+
+(*) `bool` true if successfully saved, false if validation error
+
+#### Firstrun
+
+| Event | category | method | object | value |
+|---------------------------------------------|----------|----------|---------------------|--------------|
+| Showing a first run page | action | show | firstrun | `page`* |
+| Skip button pressed | action | click | firstrun | skip |
+| Finish button pressed | action | click | firstrun | finish |
+
+(*) Page numbers start at 0. Initially when the firstrun tour is shown an event for the first page (0) is fired.
+
+#### Multitasking / Tabs
+
+| Event | category | method | object | value | extras |
+|--------------------------------------------|----------|----------|---------------------|-------|---------|
+| Context menu: Open link in new tab | action | open | browser_contextmenu | tab | `tabs`* |
+| Open the tabs tray | action | show | tabs_tray | | |
+| Close the tabs tray (back / click outside) | action | hide | tabs_tray | | |
+| Switch to tab in tabs tray | action | click | tabs_tray | tab | `tabs`* |
+| Erase history in tabs tray | action | click | tabs_tray | erase | `tabs`* |
+
+(*) `tabs` is a JSON map containing the position of the currently selected tab and the total number of open tabs:
+
+```JavaScript
+{
+ "selected": "2" // Currently selected tab (Zero-based numbering; -1 if no tab is selected)
+ "total": "5" // Total number of open tabs
+}
+```
+
+#### Homescreen Tips
+
+| Event | category | method | object | value |
+|------------------------------------------|----------|------------|--------|--------|
+| Open in new tab tip displayed | action | show | tip | open_in_new_tab_tip | |
+| Add to homescreen tip displayed | action | show | tip | add_to_homescreen_tip | |
+| Disable tracking protection tip displayed | action | show | tip | disable_tracking_protection_tip | |
+| Disable tips on home screen tip displayed | action | show | tip | disable_tips_tip | |
+| Set default browser tip displayed | action | show | tip | default_browser_tip | |
+| Autocomplete URL tip displayed | action | show | tip | add_autocomplete_url_tip | |
+| Open in new tab tip tapped | action | click | tip | open_in_new_tab_tip | |
+| Add to homescreen tip tapped | action | click | tip | add_to_homescreen_tip | |
+| Disable tips tip tapped | action | click | tip | disable_tips_tip | | |
+| Set default browser tip tapped | action | click | tip | default_browser_tip | |
+| Autocomplete URL tip tapped | action | click | tip | add_autocomplete_url_tip | |
+| Homescreen tips enabled/disabled | action | click | tip | add_to_homescreen_tip | |
+| Survey tip displayed | action | show | tip | survey_tip | |
+| Survey tip tapped | action | click | tip | survey_tip | |
+| Survey (es) tip displayed | action | show | tip | survey_tip_es | |
+| Survey (es) tip tapped | action | click | tip | survey_tip_es | |
+| Survey (fr) tip displayed | action | show | tip | survey_tip_fr | |
+| Survey (fr) tip tapped | action | click | tip | survey_tip_fr | |
+
+#### SSL Errors
+
+| Event | category | method | object | extras |
+|--------------------------------------------|----------|----------|---------|---------|
+| SSL Error From Page | error | page | browser |`error`*|
+| SSL Error From Resource | error | resource | browser |`error`* |
+
+(*)`error` is a JSON map containing the primary SSL Error
+
+```JavaScript
+{
+ "error_code": "SSL_DATE_INVALID" // Primary SSL Error
+}
+```
+
+| Possible Error Codes |
+|----------------------|
+| SSL_DATE_INVALID |
+| SSL_EXPIRED |
+|SSL_IDMISMATCH |
+|SSL_NOTYETVALID |
+|SSL_UNTRUSTED |
+|SSL_INVALID |
+|Undefined SSL Error |
+
+### Limits
+
+* An event ping will contain up to but no more than 500 events
+* No more than 40 pings per type (core/event) are stored on disk for upload at a later time
+* No more than 100 pings are sent per day
+
+## Implementation notes
+
+* Event pings are generated (and stored on disk) whenever the onStop() callback of the main activity is triggered. This happens whenever the main screen of the app is no longer visible (The app is in the background or another screen is displayed on top of the app).
+
+* Whenever we are storing pings we are also scheduling an upload. We are using Android’s JobScheduler API for that. This allows the system to run the background task whenever it is convenient and certain criterias are met. The only criteria we are specifying is that we require an active network connection. In most cases this job is executed immediately after the app is in the background.
+
+* Whenever an upload fails we are scheduling a retry. The first retry will happen after 30 seconds (or later if there’s no active network connection at this time). For further retries a exponential backoff policy is used: [30 seconds] * 2 ^ (num_failures - 1)
+
+* An earlier retry of the upload can happen whenever the app is coming to the foreground and sent to the background again (the previous scheduled job is reset and we are starting all over again).
diff --git a/mobile/android/focus-android/docs/UI-Test.md b/mobile/android/focus-android/docs/UI-Test.md
new file mode 100644
index 0000000000..a6b126297b
--- /dev/null
+++ b/mobile/android/focus-android/docs/UI-Test.md
@@ -0,0 +1,54 @@
+# UI Test
+
+To run automated tests on firefox focus, you will need a few things to get started.
+1. Working installation of git and a local clone of the this Focus repository. You can do:
+
+ git clone https://github.com/mozilla-mobile/focus-android.git
+
+2. For this walkthrough, you will also need Android Studio, which can be found at [android website](https://developer.android.com/studio) . Follow the instructions. For the most part, Android Studio is going to set itself up with the repository all you have to do is open the directory. You may also need to install JDK as well.
+
+3. From there you will need either an android device (Preferably Nexus 4 or more recent) or setup emulators in Android Studio
+To create an emulator:
+ 1. Open **Android Studio**, and navigate to the **Tools** drop down menu
+ 2. Navigate to **Android** pop out selection on the list and select "**AVD Manager**". A window will open from which you can manage your virtual devices
+ 3. select "**Create Virtual Device**"
+ 4. Select the phone tab for devices and select either the nexus devices or Google Pixel (preferably)
+ 5. you will need to select images with API level 22 ~ 26, download and install them by selecting download next to their names.
+ 6. Select the images to be installed, and click next.
+ 7. You can set the name of the device and the configuration, but is recommended to set following configurations changed:
+ * **Camera: None (For both Front and Back)**
+ * **Graphics: Automatic**
+ * **Multi-Core CPU: Unchecked**
+
+ 8. **Device Testing**: If you wish to run tests on device, you need to enable Developer Options in the Settings menu (this is usually achieved by tapping **About Phone** -> **Build Number** 7 times), and enable '**USB Debugging**.' Then connect the phone to PC via USB cable.
+
+ Our CI currently uses the following HW/SW configuration:
+
+ _Firefox Focus_
+ * Nexus 6, API 23 simulator
+ * Pixel 2, API 26 simulator
+
+ _Firefox Klar_
+ * Nexus 9, virtual, API Level 26
+
+4. You are now ready to run the automated tests!
+ 1. Click the bottom left corner of Android Studio, and enable "**Build Variants**." The Build Variants value should be set to one of the following:
+
+ on device: '**focusArmDebug**' or '**klarArmDebug**'
+
+ simulator: '**focusX86Debug**' or '**klarX86Debug**'
+
+ 2. Click on the drop down on the top left of Android Studio below the path view, and select '**Tests**'
+ 3. navigate the sub-directories to the folder locations of the tests to be run. For example, UI Tests are located in **focus-android/app/src/androidTest/java/org.mozilla.focus.activity**
+ 4. To run all tests in folder right click on the folder and select run. For individual tests you will do the same but right click on the specific test.
+ 5. select your simulator or device (connected via ADB).
+
+From there Android Studio will compile and install Focus on the simulator or device and start running the tests.
+
+If you just need to run Focus, select '**app**' from the **Configurations** pull-down menu and press Run button.
+
+You may need to follow along the first time to grant the app permissions to access the different parts of android such as storage access or the tests will fail, or manually turn on Storage permissions for Focus.
+
+It should be noted that not all tests will pass in simulator environment. Those tests are designed to run on our CI setup, and while most will not have issues, **BadURLTest** will fail on vanilla simulator environment where no Google Play store is installed, because it'll try to decode market:// url. Also, **AdBlockingTest** is suppressed, because while it passed locally, it was failing on our CI due to the network setup. I have left the test script intact for future references.
+
+(Drafted by [1Jamie](https://github.com/1jamie))
diff --git a/mobile/android/focus-android/docs/index.rst b/mobile/android/focus-android/docs/index.rst
new file mode 100644
index 0000000000..2243097b6a
--- /dev/null
+++ b/mobile/android/focus-android/docs/index.rst
@@ -0,0 +1,28 @@
+=================================
+Focus for Android
+=================================
+
+Specific documentation on a few topics is available at:
+
+.. toctree::
+ :maxdepth: 1
+
+ Adjust Usage
+ Architecture Decisions
+ Battery Debugging
+ Content blocking
+ Crash Reporting with Sentry
+ Development Custom GeckoView
+ Feature & Issue workflow
+ Running GeckoView in a debug environment
+ Home
+ Homescreen Tips
+ Multisession architecture
+ Recommended pre-push hook
+ Release Process
+ Release tracks
+ Removing strings
+ Sprint Process
+ Telemetry
+ UI Test
+ l10n Screenshot Generation
diff --git a/mobile/android/focus-android/docs/l10n-Screenshot-Generation.md b/mobile/android/focus-android/docs/l10n-Screenshot-Generation.md
new file mode 100644
index 0000000000..432358dd59
--- /dev/null
+++ b/mobile/android/focus-android/docs/l10n-Screenshot-Generation.md
@@ -0,0 +1,29 @@
+# l10n Screenshot Generation
+
+This wiki describes the steps to generate l10n screenshots on a device.
+
+## Prerequisites
+You need to install [Fastlane Screengrab](https://docs.fastlane.tools/getting-started/android/screenshots/) in order to run screenshots test. Make sure all dependencies are installed as well. Gradle dependencies and screengrab config file is in the root folder of the repo.
+
+Open a console, go to Focus folder, and type ```export LC_ALL="en_US.UTF-8"``` . Fastlane needs this value to be set, otherwise the test will crash on some locales.
+
+Enable [this](https://github.com/mozilla-mobile/focus-android/blob/master/app/src/androidTest/java/org/mozilla/focus/screenshots/ScreenshotTest.java#L71) line and disable the line below. Make sure the import is properly added. ```HostScreencapScreenshotStrategy``` was developed to generate screenshots on Taskcluster, but it is currently not being used. In order to take device screenshots, you need ```UiAutomatorScreenshotStrategy```.
+
+## Build
+Build test runner and apk by executing ```./gradlew assembleFocusArmDebug assembleFocusArmDebugAndroidTest```
+
+## Configure Screengrabfile
+```Screengrabfile``` contains the configuration for Screengrab execution, including locale list. The locale list can be found in: https://pontoon.mozilla.org/projects/focus-for-android/. Make sure the locales array is up to date.
+
+Note, that, ``` clear_previous_screenshots``` is set to true initially, but if you ran the tests and a handful of locales have failed, instead of rerunning the whole test, you should update locales array to only contain failed locales, and set this value to false. This way you won't lose successful locale screenshots.
+
+## Execute Test
+Make sure the device is connected via USB, and run ```bundle exec fastlane screengrab run``` command. Currently, this will take about 3 hours to run through all locales.
+
+## Compress outputs
+The screenshots are located in ```fastlane/metadata/android``` folder. Go to this folder, and resize the image to reduce the file size by running ```find . -name "*.png" | xargs mogrify -resize 50%```
+
+On OS X, you can compress the file size further by using [ImageOptim](https://imageoptim.com/mac). ImageOptim has a bug where too many files are loaded, it slows down and eventually cannot process images. To circumvent this issue, ```find . -type f -iname \*png -print0 | xargs -0 -t -n 100 /Applications/ImageOptim.app/Contents/MacOS/ImageOptim``` command can be run on ```android``` folder, where it'll open only 100 images, close, and reopen with next 100 images.
+
+Rename ```screenshots.html``` to ```index.html```
+Upload the screenshots to https://github.com/npark-mozilla/npark-mozilla.github.io repo, if needed for general viewing. Or you can choose to upload to Google drive and share link.
diff --git a/mobile/android/focus-android/gradle.properties b/mobile/android/focus-android/gradle.properties
new file mode 100644
index 0000000000..c94089f8d9
--- /dev/null
+++ b/mobile/android/focus-android/gradle.properties
@@ -0,0 +1,33 @@
+# 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/.
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx7g -Xms2g -XX:MaxMetaspaceSize=6g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+libUrl=https://github.com/mozilla-mobile/firefox-android/tree/main/focus-android
+libVcsUrl=https://github.com/mozilla-mobile/firefox-android.git
+libLicense=MPL-2.0
+libLicenseUrl=https://www.mozilla.org/en-US/MPL/2.0/
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
+android.enableR8.fullMode=false
+
+# Disable problematic AGP 8.3 optimization
+android.enableNewResourceShrinker.preciseShrinking=false
diff --git a/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.jar b/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..e6441136f3
Binary files /dev/null and b/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..b82aa23a4f
--- /dev/null
+++ b/mobile/android/focus-android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/mobile/android/focus-android/gradlew b/mobile/android/focus-android/gradlew
new file mode 100755
index 0000000000..1aa94a4269
--- /dev/null
+++ b/mobile/android/focus-android/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/mobile/android/focus-android/gradlew.bat b/mobile/android/focus-android/gradlew.bat
new file mode 100644
index 0000000000..7101f8e467
--- /dev/null
+++ b/mobile/android/focus-android/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/mobile/android/focus-android/l10n.toml b/mobile/android/focus-android/l10n.toml
new file mode 100644
index 0000000000..d354ade04e
--- /dev/null
+++ b/mobile/android/focus-android/l10n.toml
@@ -0,0 +1,118 @@
+basepath = "."
+
+locales = [
+ "ace",
+ "af",
+ "am",
+ "an",
+ "anp",
+ "ar",
+ "ast",
+ "ay",
+ "az",
+ "be",
+ "bg",
+ "bn",
+ "bo",
+ "bs",
+ "ca",
+ "cak",
+ "co",
+ "cs",
+ "cy",
+ "da",
+ "de",
+ "dsb",
+ "el",
+ "en-CA",
+ "en-GB",
+ "eo",
+ "es-AR",
+ "es-CL",
+ "es-ES",
+ "es-MX",
+ "et",
+ "eu",
+ "fa",
+ "fi",
+ "fr",
+ "fur",
+ "fy-NL",
+ "ga-IE",
+ "gl",
+ "gu-IN",
+ "he",
+ "hi-IN",
+ "hr",
+ "hsb",
+ "hu",
+ "hus",
+ "hy-AM",
+ "ia",
+ "id",
+ "is",
+ "it",
+ "ixl",
+ "ja",
+ "jv",
+ "ka",
+ "kaa",
+ "kab",
+ "kk",
+ "ko",
+ "kw",
+ "lo",
+ "lt",
+ "meh",
+ "mix",
+ "mr",
+ "ms",
+ "my",
+ "nb-NO",
+ "ne-NP",
+ "nl",
+ "nn-NO",
+ "nv",
+ "oc",
+ "pa-IN",
+ "pai",
+ "pl",
+ "ppl",
+ "pt-BR",
+ "quc",
+ "quy",
+ "ro",
+ "ru",
+ "si",
+ "sk",
+ "skr",
+ "sl",
+ "sn",
+ "sq",
+ "sr",
+ "su",
+ "sv-SE",
+ "ta",
+ "te",
+ "tg",
+ "th",
+ "tr",
+ "trs",
+ "tsz",
+ "tt",
+ "uk",
+ "ur",
+ "vi",
+ "wo",
+ "yua",
+ "zam",
+ "zh-CN",
+ "zh-HK",
+ "zh-TW",
+]
+
+[env]
+
+[[paths]]
+ reference = "app/src/main/res/values/strings.xml"
+ l10n = "app/src/main/res/values-{android_locale}/strings.xml"
diff --git a/mobile/android/focus-android/plugins/focusdependencies/build.gradle b/mobile/android/focus-android/plugins/focusdependencies/build.gradle
new file mode 100644
index 0000000000..7b9006f55c
--- /dev/null
+++ b/mobile/android/focus-android/plugins/focusdependencies/build.gradle
@@ -0,0 +1,25 @@
+/* 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/. */
+
+plugins {
+ id "org.gradle.kotlin.kotlin-dsl" version "4.2.1"
+}
+
+repositories {
+ gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
+ maven {
+ url repository
+ if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
+ allowInsecureProtocol = true
+ }
+ }
+ }
+}
+
+gradlePlugin {
+ plugins.register("FocusDependenciesPlugin") {
+ id = "FocusDependenciesPlugin"
+ implementationClass = "FocusDependenciesPlugin"
+ }
+}
diff --git a/mobile/android/focus-android/plugins/focusdependencies/settings.gradle b/mobile/android/focus-android/plugins/focusdependencies/settings.gradle
new file mode 100644
index 0000000000..16701d4aac
--- /dev/null
+++ b/mobile/android/focus-android/plugins/focusdependencies/settings.gradle
@@ -0,0 +1,19 @@
+/* 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/. */
+
+// Prevents gradle builds from looking for a root settings.gradle
+pluginManagement {
+ apply from: file('../../../gradle/mozconfig.gradle')
+
+ repositories {
+ gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
+ maven {
+ url repository
+ if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
+ allowInsecureProtocol = true
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt b/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt
new file mode 100644
index 0000000000..ecc69fe4f1
--- /dev/null
+++ b/mobile/android/focus-android/plugins/focusdependencies/src/main/java/FocusDependenciesPlugin.kt
@@ -0,0 +1,68 @@
+/* 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/. */
+
+import org.gradle.api.Plugin
+import org.gradle.api.initialization.Settings
+
+// If you ever need to force a toolchain rebuild (taskcluster) then edit the following comment.
+// FORCE REBUILD 2023-05-05
+
+class FocusDependenciesPlugin : Plugin {
+ override fun apply(settings: Settings) = Unit
+}
+
+object FocusVersions {
+ object Adjust {
+ const val adjust = "4.38.2"
+ const val install_referrer = "2.2"
+ }
+
+ object AndroidX {
+ const val constraint_layout_compose = "1.0.1"
+ const val splashscreen = "1.0.1"
+ const val transition = "1.4.1"
+ }
+
+ object Google {
+ const val play = "1.10.3"
+ }
+
+ object Testing {
+ const val androidx_espresso = "3.5.1"
+ const val androidx_orchestrator = "1.4.2"
+ const val falcon = "2.2.0"
+ const val fastlane = "2.1.1"
+ const val junit = "5.10.2"
+ }
+
+ object ThirdParty {
+ const val osslicenses_plugin = "0.10.4"
+ }
+}
+
+object FocusDependencies {
+ const val androidx_constraint_layout_compose =
+ "androidx.constraintlayout:constraintlayout-compose:${FocusVersions.AndroidX.constraint_layout_compose}"
+
+ const val androidx_splashscreen = "androidx.core:core-splashscreen:${FocusVersions.AndroidX.splashscreen}"
+ const val androidx_transition = "androidx.transition:transition:${FocusVersions.AndroidX.transition}"
+
+ const val google_play = "com.google.android.play:core:${FocusVersions.Google.play}"
+
+ const val adjust = "com.adjust.sdk:adjust-android:${FocusVersions.Adjust.adjust}"
+ const val install_referrer = "com.android.installreferrer:installreferrer:${FocusVersions.Adjust.install_referrer}"
+ const val osslicenses_plugin = "com.google.android.gms:oss-licenses-plugin:${FocusVersions.ThirdParty.osslicenses_plugin}"
+
+ const val androidx_orchestrator = "androidx.test:orchestrator:${FocusVersions.Testing.androidx_orchestrator}"
+ const val espresso_contrib = "androidx.test.espresso:espresso-contrib:${FocusVersions.Testing.androidx_espresso}"
+ const val espresso_idling_resource = "androidx.test.espresso:espresso-idling-resource:${FocusVersions.Testing.androidx_espresso}"
+ const val espresso_intents = "androidx.test.espresso:espresso-intents:${FocusVersions.Testing.androidx_espresso}"
+ const val espresso_web = "androidx.test.espresso:espresso-web:${FocusVersions.Testing.androidx_espresso}"
+ const val falcon = "com.jraska:falcon:${FocusVersions.Testing.falcon}"
+ const val fastlane = "tools.fastlane:screengrab:${FocusVersions.Testing.fastlane}"
+
+ const val testing_junit_api = "org.junit.jupiter:junit-jupiter-api:${FocusVersions.Testing.junit}"
+ const val testing_junit_engine = "org.junit.jupiter:junit-jupiter-engine:${FocusVersions.Testing.junit}"
+ const val testing_junit_params = "org.junit.jupiter:junit-jupiter-params:${FocusVersions.Testing.junit}"
+}
diff --git a/mobile/android/focus-android/quality/checkstyle.xml b/mobile/android/focus-android/quality/checkstyle.xml
new file mode 100644
index 0000000000..b1728aebc1
--- /dev/null
+++ b/mobile/android/focus-android/quality/checkstyle.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/quality/detekt-baseline.xml b/mobile/android/focus-android/quality/detekt-baseline.xml
new file mode 100644
index 0000000000..91d986d5cb
--- /dev/null
+++ b/mobile/android/focus-android/quality/detekt-baseline.xml
@@ -0,0 +1,406 @@
+
+
+
+
+
+
+ AbsentOrWrongFileLicense:LocalesList.kt$org.mozilla.focus.generated.LocalesList.kt
+ CollapsibleIfStatements:MainActivity.kt$MainActivity$if (!isTaskRoot) { if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN == intent.action) { finish() return } }
+ CollapsibleIfStatements:UrlInputFragment.kt$UrlInputFragment.<no name provided>$if (reverse) { if (isOverlay) { dismiss() } }
+ EmptyFunctionBlock:AutocompleteListFragment.kt$AutocompleteListFragment.<no name provided>${}
+ EmptyFunctionBlock:ExceptionsListFragment.kt$ExceptionsListFragment.<no name provided>${}
+ EmptyFunctionBlock:FirstrunFragment.kt$FirstrunFragment.<no name provided>${}
+ MayBeConst:CrashReporterFragment.kt$CrashReporterFragment.Companion$val FRAGMENT_TAG = "crash-reporter"
+ MayBeConst:IconGenerator.kt$IconGenerator.Companion$private val DEFAULT_ICON_CHAR = '?'
+ MayBeConst:IconGenerator.kt$IconGenerator.Companion$private val TEXT_SIZE_DP = 36f
+ MayBeConst:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference.Companion$private val SEARCH_ENGINE_NAME_KEY = "search-engine-name"
+ MayBeConst:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference.Companion$private val SEARCH_QUERY_KEY = "search-query"
+ MayBeConst:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference.Companion$private val SUPER_STATE_KEY = "super-state"
+ OutdatedDocumentation:AppAdapter.kt$AppAdapter : Adapter
+ OutdatedDocumentation:AppReviewUtils.kt$AppReviewUtils.Companion$fun addAppOpenings(context: Context)
+ OutdatedDocumentation:AppReviewUtils.kt$AppReviewUtils.Companion$fun showAppReview(activity: Activity)
+ OutdatedDocumentation:AppState.kt$AppState : State
+ OutdatedDocumentation:AppState.kt$Screen
+ OutdatedDocumentation:AppState.kt$Screen$Locked : Screen
+ OutdatedDocumentation:DefaultTopSitesStorage.kt$DefaultTopSitesStorage : TopSitesStorageObservable
+ OutdatedDocumentation:FocusDialog.kt$@Suppress("LongParameterList") @Composable fun FocusDialog( dialogTitle: String, dialogTextComposable: @Composable (() -> Unit)? = null, dialogText: String = "", onConfirmRequest: () -> Unit, onDismissRequest: () -> Unit, confirmButtonText: String = "null", dismissButtonText: String = "null", isConfirmButtonEnabled: Boolean = true, isDismissButtonEnabled: Boolean = true, isConfirmButtonVisible: Boolean = true, isDismissButtonVisible: Boolean = true, )
+ OutdatedDocumentation:HtmlLoader.kt$HtmlLoader$fun loadResourceFile( context: Context, @RawRes resourceID: Int, substitutionTable: Map<String, String>, ): String
+ OutdatedDocumentation:Language.kt$Language
+ OutdatedDocumentation:LanguageListItem.kt$LanguageListItem
+ OutdatedDocumentation:LanguageStorage.kt$LanguageStorage$fun saveCurrentLanguageInSharePref(languageTag: String)
+ OutdatedDocumentation:PrivateNotificationFeature.kt$PrivateNotificationFeature : LifecycleAwareFeature
+ OutdatedDocumentation:SitePermissionOptionsFragmentCompose.kt$@Composable fun OptionsPermissionList( optionsListItems: List<SitePermissionOptionListItem>, state: MutableState<Int>, permissionLabel: String?, goToPhoneSettings: () -> Unit, componentPermissionBlockedByAndroidVisibility: Boolean, )
+ UndocumentedPublicClass:AboutFragment.kt$AboutFragment : BaseSettingsLikeFragment
+ UndocumentedPublicClass:AdvancedSettingsFragment.kt$AdvancedSettingsFragment : BaseSettingsFragmentOnSharedPreferenceChangeListener
+ UndocumentedPublicClass:AppAction.kt$AppAction$NavigateUp : AppAction
+ UndocumentedPublicClass:AppAction.kt$AppAction$OpenSettings : AppAction
+ UndocumentedPublicClass:AppAction.kt$AppAction$OpenSitePermissionOptionsScreen : AppAction
+ UndocumentedPublicClass:AppConstants.kt$AppConstants
+ UndocumentedPublicClass:AppContentInterceptor.kt$AppContentInterceptor : RequestInterceptor
+ UndocumentedPublicClass:AppReviewStep.kt$AppReviewStep
+ UndocumentedPublicClass:AppReviewUtils.kt$AppReviewUtils
+ UndocumentedPublicClass:AppStartReasonProvider.kt$AppStartReasonProvider$StartReason
+ UndocumentedPublicClass:AppState.kt$Screen$Settings : Screen
+ UndocumentedPublicClass:AppState.kt$Screen$SitePermissionOptionsScreen : Screen
+ UndocumentedPublicClass:AppState.kt$Screen.Settings$Page
+ UndocumentedPublicClass:AutocompleteDomainFormatter.kt$AutocompleteDomainFormatter
+ UndocumentedPublicClass:AutocompleteRemoveFragment.kt$AutocompleteRemoveFragment : AutocompleteListFragmentCoroutineScope
+ UndocumentedPublicClass:BaseFragment.kt$BaseFragment : Fragment
+ UndocumentedPublicClass:BaseSettingsFragment.kt$BaseSettingsFragment : PreferenceFragmentCompatMenuProvider
+ UndocumentedPublicClass:BrowserMenuController.kt$BrowserMenuController
+ UndocumentedPublicClass:BrowserToolbarIntegration.kt$BrowserToolbarIntegration : LifecycleAwareFeature
+ UndocumentedPublicClass:Config.kt$Config
+ UndocumentedPublicClass:ConnectionDetailsPanel.kt$ConnectionDetailsPanel : BottomSheetDialog
+ UndocumentedPublicClass:ContextMenuCandidates.kt$ContextMenuCandidates
+ UndocumentedPublicClass:CookieBannerExceptionDetailsSwitch.kt$CookieBannerExceptionDetailsSwitch : ConstraintLayout
+ UndocumentedPublicClass:CookieBannerFragment.kt$CookieBannerFragment : BaseSettingsFragment
+ UndocumentedPublicClass:CookieBannerOption.kt$CookieBannerOption
+ UndocumentedPublicClass:CookieBannerOption.kt$CookieBannerOption$CookieBannerDisabled : CookieBannerOption
+ UndocumentedPublicClass:CookieBannerOption.kt$CookieBannerOption$CookieBannerRejectAll : CookieBannerOption
+ UndocumentedPublicClass:CookieBannerRejectAllPreference.kt$CookieBannerRejectAllPreference : LearnMoreSwitchPreference
+ UndocumentedPublicClass:CrashListActivity.kt$CrashListActivity : AbstractCrashListActivity
+ UndocumentedPublicClass:CrashReporterFragment.kt$CrashReporterFragment : Fragment
+ UndocumentedPublicClass:CustomTabMenu.kt$CustomTabMenu : ToolbarMenu
+ UndocumentedPublicClass:CustomTabsService.kt$CustomTabsService : AbstractCustomTabsService
+ UndocumentedPublicClass:DebugFocusApplication.kt$DebugFocusApplication : FocusApplication
+ UndocumentedPublicClass:DefaultBrowserPreference.kt$DefaultBrowserPreference : Preference
+ UndocumentedPublicClass:DefaultLanguageScreenInteractor.kt$DefaultLanguageScreenInteractor
+ UndocumentedPublicClass:DefaultSitePermissionOptionsScreenInteractor.kt$DefaultSitePermissionOptionsScreenInteractor
+ UndocumentedPublicClass:DownloadService.kt$DownloadService : AbstractFetchDownloadService
+ UndocumentedPublicClass:EngineProvider.kt$EngineProvider
+ UndocumentedPublicClass:EngineSharedPreferencesListener.kt$EngineSharedPreferencesListener$ChangeSource
+ UndocumentedPublicClass:EngineSharedPreferencesListener.kt$EngineSharedPreferencesListener$TrackerChanged
+ UndocumentedPublicClass:EraseAndOpenShortcutActivity.kt$EraseAndOpenShortcutActivity : Activity
+ UndocumentedPublicClass:EraseShortcutActivity.kt$EraseShortcutActivity : Activity
+ UndocumentedPublicClass:ExceptionsRemoveFragment.kt$ExceptionsRemoveFragment : ExceptionsListFragment
+ UndocumentedPublicClass:FenixProductDetector.kt$FenixProductDetector
+ UndocumentedPublicClass:FenixProductDetector.kt$FenixProductDetector$FenixVersion
+ UndocumentedPublicClass:FindInPageIntegration.kt$FindInPageIntegration : LifecycleAwareFeatureUserInteractionHandler
+ UndocumentedPublicClass:FirstrunCardView.kt$FirstrunCardView : CardView
+ UndocumentedPublicClass:FirstrunFragment.kt$FirstrunFragment : FragmentOnClickListener
+ UndocumentedPublicClass:FirstrunPagerAdapter.kt$FirstrunPagerAdapter : PagerAdapter
+ UndocumentedPublicClass:FocusApplication.kt$FocusApplication : LocaleAwareApplicationProviderCoroutineScope
+ UndocumentedPublicClass:FocusSnackbar.kt$FocusSnackbar : BaseTransientBottomBar
+ UndocumentedPublicClass:FocusSnackbarDelegate.kt$FocusSnackbarDelegate : SnackbarDelegate
+ UndocumentedPublicClass:FullScreenIntegration.kt$FullScreenIntegration : LifecycleAwareFeatureUserInteractionHandler
+ UndocumentedPublicClass:GeneralSettingsFragment.kt$GeneralSettingsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:HardwarePermissionCheckFeature.kt$HardwarePermissionCheckFeature : DefaultLifecycleObserver
+ UndocumentedPublicClass:HomeMenuItem.kt$HomeMenuItem
+ UndocumentedPublicClass:HomeMenuItem.kt$HomeMenuItem$Help : HomeMenuItem
+ UndocumentedPublicClass:HomeMenuItem.kt$HomeMenuItem$Settings : HomeMenuItem
+ UndocumentedPublicClass:IconGenerator.kt$IconGenerator
+ UndocumentedPublicClass:InputToolbarIntegration.kt$InputToolbarIntegration : LifecycleAwareFeature
+ UndocumentedPublicClass:InstalledSearchEnginesSettingsFragment.kt$InstalledSearchEnginesSettingsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:IntentProcessor.kt$IntentProcessor$Result
+ UndocumentedPublicClass:IntentProcessor.kt$IntentProcessor.Result$CustomTab : Result
+ UndocumentedPublicClass:IntentProcessor.kt$IntentProcessor.Result$None : Result
+ UndocumentedPublicClass:IntentProcessor.kt$IntentProcessor.Result$Tab : Result
+ UndocumentedPublicClass:IntentUtils.kt$IntentUtils
+ UndocumentedPublicClass:LanguageFragment.kt$LanguageFragment : BaseComposeFragment
+ UndocumentedPublicClass:LanguageMiddleware.kt$LanguageMiddleware : Middleware
+ UndocumentedPublicClass:LanguageScreenStore.kt$LanguageScreenAction$InitLanguages : LanguageScreenAction
+ UndocumentedPublicClass:LanguageScreenStore.kt$LanguageScreenAction$Select : LanguageScreenAction
+ UndocumentedPublicClass:LanguageScreenStore.kt$LanguageScreenAction$UpdateLanguages : LanguageScreenAction
+ UndocumentedPublicClass:LanguageScreenStore.kt$LanguageScreenStore : Store
+ UndocumentedPublicClass:LanguageStorage.kt$LanguageStorage
+ UndocumentedPublicClass:LearnMoreSwitchPreference.kt$LearnMoreSwitchPreference : SwitchPreferenceCompat
+ UndocumentedPublicClass:LocaleDescriptor.kt$LocaleDescriptor : Comparable
+ UndocumentedPublicClass:LocalizedContent.kt$LocalizedContent
+ UndocumentedPublicClass:LockObserver.kt$LockObserver : DefaultLifecycleObserver
+ UndocumentedPublicClass:MainActivity.kt$MainActivity : LocaleAwareAppCompatActivity
+ UndocumentedPublicClass:MainActivity.kt$MainActivity$AppOpenType
+ UndocumentedPublicClass:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference : Preference
+ UndocumentedPublicClass:ManualAddSearchEngineSettingsFragment.kt$ManualAddSearchEngineSettingsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:MobileMetricsPingStorage.kt$MobileMetricsPingStorage
+ UndocumentedPublicClass:MozillaPreference.kt$MozillaPreference : Preference
+ UndocumentedPublicClass:MozillaSettingsFragment.kt$MozillaSettingsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:MultiselectSearchEngineListPreference.kt$MultiselectSearchEngineListPreference : SearchEngineListPreference
+ UndocumentedPublicClass:NavigationButtonsIntegration.kt$NavigationButtonsIntegration : LifecycleAwareFeature
+ UndocumentedPublicClass:OnboardingController.kt$DefaultOnboardingController : OnboardingController
+ UndocumentedPublicClass:OnboardingController.kt$OnboardingController
+ UndocumentedPublicClass:OnboardingFirstFragment.kt$OnboardingFirstFragment : Fragment
+ UndocumentedPublicClass:OnboardingInteractor.kt$DefaultOnboardingInteractor : OnboardingInteractor
+ UndocumentedPublicClass:OnboardingInteractor.kt$OnboardingInteractor
+ UndocumentedPublicClass:OnboardingSecondFragment.kt$OnboardingSecondFragment : Fragment
+ UndocumentedPublicClass:OnboardingStep.kt$OnboardingStep
+ UndocumentedPublicClass:OnboardingStorage.kt$OnboardingStorage
+ UndocumentedPublicClass:PreferenceSwitch.kt$PreferenceSwitch : SwitchCompat
+ UndocumentedPublicClass:PreferenceToolTipCompose.kt$PreferenceToolTipCompose : Preference
+ UndocumentedPublicClass:PrivacySecuritySettingsFragment.kt$PrivacySecuritySettingsFragment : BaseSettingsFragmentOnSharedPreferenceChangeListener
+ UndocumentedPublicClass:RadioButtonPreference.kt$GroupableRadioButton
+ UndocumentedPublicClass:RadioButtonPreference.kt$RadioButtonPreference : PreferenceGroupableRadioButton
+ UndocumentedPublicClass:RadioSearchEngineListPreference.kt$RadioSearchEngineListPreference : SearchEngineListPreferenceOnCheckedChangeListener
+ UndocumentedPublicClass:RemoveSearchEnginesSettingsFragment.kt$RemoveSearchEnginesSettingsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:SearchEngineListPreference.kt$SearchEngineListPreference : PreferenceCoroutineScope
+ UndocumentedPublicClass:SearchSettingsFragment.kt$SearchSettingsFragment : BaseSettingsFragmentOnSharedPreferenceChangeListener
+ UndocumentedPublicClass:SearchSuggestionsFragment.kt$SearchSuggestionsFragment : FragmentCoroutineScope
+ UndocumentedPublicClass:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences
+ UndocumentedPublicClass:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel : AndroidViewModel
+ UndocumentedPublicClass:SearchSuggestionsViewModel.kt$State
+ UndocumentedPublicClass:SearchSuggestionsViewModel.kt$State$Disabled : State
+ UndocumentedPublicClass:SearchSuggestionsViewModel.kt$State$NoSuggestionsAPI : State
+ UndocumentedPublicClass:SearchSuggestionsViewModel.kt$State$ReadyForSuggestions : State
+ UndocumentedPublicClass:SearchUtils.kt$SearchUtils
+ UndocumentedPublicClass:SearchWidgetProvider.kt$SearchWidgetProvider : AppSearchWidgetProvider
+ UndocumentedPublicClass:SearchWidgetUtils.kt$SearchWidgetUtils
+ UndocumentedPublicClass:SecretSettingsFragment.kt$SecretSettingsFragment : BaseSettingsFragmentOnSharedPreferenceChangeListener
+ UndocumentedPublicClass:SettingsFragment.kt$SettingsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:SitePermission.kt$SitePermission : Parcelable
+ UndocumentedPublicClass:SitePermissionOption.kt$AutoplayOption
+ UndocumentedPublicClass:SitePermissionOption.kt$AutoplayOption$AllowAudioVideo : SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOption.kt$AutoplayOption$BlockAudioOnly : SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOption.kt$AutoplayOption$BlockAudioVideo : SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOption.kt$SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOption.kt$SitePermissionOption$Allowed : SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOption.kt$SitePermissionOption$AskToAllow : SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOption.kt$SitePermissionOption$Blocked : SitePermissionOption
+ UndocumentedPublicClass:SitePermissionOptionListItem.kt$SitePermissionOptionListItem
+ UndocumentedPublicClass:SitePermissionOptionsFragment.kt$SitePermissionOptionsFragment : BaseComposeFragment
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenAction : Action
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenAction$AndroidPermission : SitePermissionOptionsScreenAction
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenAction$InitSitePermissionOptions : SitePermissionOptionsScreenAction
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenAction$Select : SitePermissionOptionsScreenAction
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenAction$UpdateSitePermissionOptions : SitePermissionOptionsScreenAction
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenState : State
+ UndocumentedPublicClass:SitePermissionOptionsScreenStore.kt$SitePermissionOptionsScreenStore : Store
+ UndocumentedPublicClass:SitePermissionOptionsStorage.kt$SitePermissionOptionsStorage
+ UndocumentedPublicClass:SitePermissionOptionsStorageMiddleware.kt$SitePermissionOptionsStorageMiddleware : Middleware
+ UndocumentedPublicClass:SitePermissionsFragment.kt$SitePermissionsFragment : BaseSettingsFragment
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog$StartupLogActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog$StartupLogAppLifecycleObserver : DefaultLifecycleObserver
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog.LogEntry$ActivityCreated : LogEntry
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog.LogEntry$ActivityStarted : LogEntry
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog.LogEntry$ActivityStopped : LogEntry
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog.LogEntry$AppStarted : LogEntry
+ UndocumentedPublicClass:StartupActivityLog.kt$StartupActivityLog.LogEntry$AppStopped : LogEntry
+ UndocumentedPublicClass:StartupPathProvider.kt$StartupPathProvider$StartupPath
+ UndocumentedPublicClass:StartupPathProvider.kt$StartupPathProvider$StartupPathLifecycleObserver : DefaultLifecycleObserver
+ UndocumentedPublicClass:StartupStateProvider.kt$StartupStateProvider$StartupState
+ UndocumentedPublicClass:StartupTypeTelemetry.kt$StartupTypeTelemetry$StartupTypeLifecycleObserver : DefaultLifecycleObserver
+ UndocumentedPublicClass:StudiesAdapter.kt$StudiesAdapter : ListAdapter
+ UndocumentedPublicClass:StudiesAdapter.kt$StudiesAdapter$StudiesDiffCallback : ItemCallback
+ UndocumentedPublicClass:StudiesFragment.kt$StudiesFragment : BaseSettingsLikeFragment
+ UndocumentedPublicClass:StudiesListItem.kt$StudiesListItem
+ UndocumentedPublicClass:StudiesListItem.kt$StudiesListItem$ActiveStudy : StudiesListItem
+ UndocumentedPublicClass:StudiesListItem.kt$StudiesListItem$Section : StudiesListItem
+ UndocumentedPublicClass:StudiesRecyclerView.kt$StudiesRecyclerView : RecyclerView
+ UndocumentedPublicClass:StudiesViewHolder.kt$StudiesViewHolder : ViewHolder
+ UndocumentedPublicClass:StudiesViewHolder.kt$StudiesViewHolder$ActiveStudiesViewHolder : StudiesViewHolder
+ UndocumentedPublicClass:StudiesViewHolder.kt$StudiesViewHolder$SectionViewHolder : StudiesViewHolder
+ UndocumentedPublicClass:StudiesViewModel.kt$StudiesViewModel : AndroidViewModel
+ UndocumentedPublicClass:SupportUtils.kt$SupportUtils
+ UndocumentedPublicClass:SupportUtils.kt$SupportUtils$SumoTopic
+ UndocumentedPublicClass:SwitchWithDescription.kt$SwitchWithDescription : ConstraintLayout
+ UndocumentedPublicClass:TabViewHolder.kt$TabViewHolder : ViewHolder
+ UndocumentedPublicClass:TabsPopup.kt$TabsPopup : PopupWindow
+ UndocumentedPublicClass:TelemetryMiddleware.kt$TelemetryMiddleware : Middleware
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu$CustomTabItem
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu$Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$AddToHomeScreen : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$Back : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$FindInPage : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$Forward : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$OpenInApp : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$OpenInBrowser : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$Reload : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$RequestDesktop : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.CustomTabItem$Stop : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$AddToHomeScreen : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$AddToShortcuts : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$Back : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$FindInPage : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$Forward : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$OpenInApp : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$Reload : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$RemoveFromShortcuts : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$RequestDesktop : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$Settings : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$Share : Item
+ UndocumentedPublicClass:ToolbarMenu.kt$ToolbarMenu.Item$Stop : Item
+ UndocumentedPublicClass:TrackingProtectionPanel.kt$TrackingProtectionPanel : BottomSheetDialog
+ UndocumentedPublicClass:UrlInputFragment.kt$FocusCrashException : Exception
+ UndocumentedPublicClass:ViewUtils.kt$ViewUtils
+ UndocumentedPublicClass:VoiceSearchActivity.kt$VoiceSearchActivity : BaseVoiceSearchActivity
+ UndocumentedPublicFunction:AboutFragment.kt$@Composable fun ColumnScope.LearnMoreLink( learnMore: String, openLearnMore: () -> Job, )
+ UndocumentedPublicFunction:AddToHomescreenDialogFragment.kt$AddToHomescreenDialogFragment.Companion$fun newInstance( url: String, title: String, blockingEnabled: Boolean, requestDesktop: Boolean, ): AddToHomescreenDialogFragment
+ UndocumentedPublicFunction:AdvancedSettingsFragment.kt$AdvancedSettingsFragment.Companion$fun newInstance(): AdvancedSettingsFragment
+ UndocumentedPublicFunction:AutocompleteDomainFormatter.kt$AutocompleteDomainFormatter$fun format(url: String): String
+ UndocumentedPublicFunction:AutocompleteListFragment.kt$AutocompleteListFragment.DomainListAdapter$fun move(from: Int, to: Int)
+ UndocumentedPublicFunction:AutocompleteListFragment.kt$AutocompleteListFragment.DomainListAdapter$fun refresh(context: Context, body: (() -> Unit)? = null)
+ UndocumentedPublicFunction:AutocompleteListFragment.kt$AutocompleteListFragment.DomainListAdapter$fun selection(): List<String>
+ UndocumentedPublicFunction:BaseFragment.kt$BaseFragment$fun cancelAnimation()
+ UndocumentedPublicFunction:BrowserFragment.kt$BrowserFragment$fun crashReporterIsVisible(): Boolean
+ UndocumentedPublicFunction:BrowserFragment.kt$BrowserFragment$fun erase(shouldEraseAllTabs: Boolean = false)
+ UndocumentedPublicFunction:BrowserFragment.kt$BrowserFragment$fun handleTabCrash(crash: Crash)
+ UndocumentedPublicFunction:BrowserFragment.kt$BrowserFragment$fun showTrackingProtectionPanel()
+ UndocumentedPublicFunction:BrowserFragment.kt$BrowserFragment.Companion$fun createForTab(tabId: String): BrowserFragment
+ UndocumentedPublicFunction:BrowserMenuController.kt$BrowserMenuController$@Suppress("ComplexMethod") fun handleMenuInteraction(item: ToolbarMenu.Item)
+ UndocumentedPublicFunction:BrowsersCache.kt$BrowsersCache$@Synchronized fun all(context: Context): Browsers
+ UndocumentedPublicFunction:ClientWrapper.kt$ClientWrapper$@Deprecated("Non-private Client usage should be prevented") fun unwrap(): Client
+ UndocumentedPublicFunction:CookiesPreference.kt$CookiesPreference$fun updateSummary()
+ UndocumentedPublicFunction:CrashReporterFragment.kt$CrashReporterFragment.Companion$fun create()
+ UndocumentedPublicFunction:DefaultBrowserPreference.kt$DefaultBrowserPreference$fun update()
+ UndocumentedPublicFunction:DefaultLanguageScreenInteractor.kt$DefaultLanguageScreenInteractor$fun handleLanguageSelected(language: Language)
+ UndocumentedPublicFunction:DefaultSitePermissionOptionsScreenInteractor.kt$DefaultSitePermissionOptionsScreenInteractor$fun handleSitePermissionOptionSelected(sitePermissionOption: SitePermissionOption)
+ UndocumentedPublicFunction:EngineProvider.kt$EngineProvider$fun createClient(context: Context): Client
+ UndocumentedPublicFunction:EngineProvider.kt$EngineProvider$fun createCookieBannerStorage(context: Context): GeckoCookieBannersStorage
+ UndocumentedPublicFunction:EngineProvider.kt$EngineProvider$fun createEngine(context: Context, defaultSettings: DefaultSettings): Engine
+ UndocumentedPublicFunction:ExceptionsListFragment.kt$ExceptionsListFragment.DomainListAdapter$fun move(from: Int, to: Int)
+ UndocumentedPublicFunction:ExceptionsListFragment.kt$ExceptionsListFragment.DomainListAdapter$fun refresh(context: Context, body: (() -> Unit)? = null)
+ UndocumentedPublicFunction:ExceptionsListFragment.kt$ExceptionsListFragment.DomainListAdapter$fun selection(): List<TrackingProtectionException>
+ UndocumentedPublicFunction:FactsProcessor.kt$FactsProcessor$fun initialize()
+ UndocumentedPublicFunction:FenixProductDetector.kt$FenixProductDetector$fun getInstalledFenixVersions(context: Context): List<String>
+ UndocumentedPublicFunction:FenixProductDetector.kt$FenixProductDetector$fun isFenixDefaultBrowser(defaultBrowser: ActivityInfo?): Boolean
+ UndocumentedPublicFunction:FindInPageIntegration.kt$FindInPageIntegration$fun hide()
+ UndocumentedPublicFunction:FindInPageIntegration.kt$FindInPageIntegration$fun show(sessionState: SessionState)
+ UndocumentedPublicFunction:FirstrunFragment.kt$FirstrunFragment.Companion$fun create(): FirstrunFragment
+ UndocumentedPublicFunction:FirstrunPagerAdapter.kt$FirstrunPagerAdapter$fun getPageAccessibilityDescription(position: Int): String
+ UndocumentedPublicFunction:FocusApplication.kt$FocusApplication$open fun updateLeakCanaryState(isEnabled: Boolean)
+ UndocumentedPublicFunction:FocusDialog.kt$@Composable fun FocusDialogSample()
+ UndocumentedPublicFunction:FocusDialog.kt$@Preview( name = "dark theme", showBackground = true, backgroundColor = 0xFF393473, uiMode = Configuration.UI_MODE_NIGHT_MASK, ) @Composable fun DialogTitlePreviewDark()
+ UndocumentedPublicFunction:FocusDialog.kt$@Preview( name = "light theme", showBackground = true, backgroundColor = 0xFFFBFBFE, uiMode = Configuration.UI_MODE_NIGHT_NO, ) @Composable fun DialogTitlePreviewLight()
+ UndocumentedPublicFunction:FocusSnackbar.kt$FocusSnackbar$fun setText(text: String)
+ UndocumentedPublicFunction:FocusTheme.kt$fun phoneDimensions()
+ UndocumentedPublicFunction:FocusTheme.kt$fun tabletDimensions()
+ UndocumentedPublicFunction:HomeMenu.kt$HomeMenu$fun getMenuBuilder(): BrowserMenuBuilder
+ UndocumentedPublicFunction:IconGenerator.kt$IconGenerator.Companion$@JvmStatic fun generateLauncherIconPreOreo(context: Context, character: Char): Bitmap
+ UndocumentedPublicFunction:InstallFirefoxActivity.kt$InstallFirefoxActivity.Companion$fun open(context: Context)
+ UndocumentedPublicFunction:InstallFirefoxActivity.kt$InstallFirefoxActivity.Companion$fun resolveAppStore(context: Context): ActivityInfo?
+ UndocumentedPublicFunction:InstalledSearchEnginesSettingsFragment.kt$InstalledSearchEnginesSettingsFragment.Companion$fun newInstance()
+ UndocumentedPublicFunction:LearnMoreSwitchPreference.kt$LearnMoreSwitchPreference$abstract fun getLearnMoreUrl(): String
+ UndocumentedPublicFunction:LearnMoreSwitchPreference.kt$LearnMoreSwitchPreference$open fun getDescription(): String?
+ UndocumentedPublicFunction:LocaleDescriptor.kt$LocaleDescriptor$fun getNativeName(): String?
+ UndocumentedPublicFunction:LocaleDescriptor.kt$LocaleDescriptor$fun getTag(): String
+ UndocumentedPublicFunction:LocaleFragmentCompose.kt$@Composable fun LanguageNameAndTagItem( language: Language, isSelected: Boolean, onClick: (String) -> Unit, )
+ UndocumentedPublicFunction:LocalizedContent.kt$LocalizedContent$fun loadGPL(context: Context): String
+ UndocumentedPublicFunction:LocalizedContent.kt$LocalizedContent$fun loadLicenses(context: Context): String
+ UndocumentedPublicFunction:MainActivity.kt$MainActivity$fun customizeStatusBar(backgroundColorId: Int? = null)
+ UndocumentedPublicFunction:MainActivity.kt$MainActivity$fun getToolbar(): ActionBar
+ UndocumentedPublicFunction:MainActivity.kt$MainActivity$fun hideStatusBarBackground()
+ UndocumentedPublicFunction:MainActivityNavigation.kt$MainActivityNavigation$@Suppress("ComplexMethod") fun settings(page: Screen.Settings.Page)
+ UndocumentedPublicFunction:MainActivityNavigation.kt$MainActivityNavigation$fun showOnBoardingSecondScreen()
+ UndocumentedPublicFunction:MainActivityNavigation.kt$MainActivityNavigation$fun sitePermissionOptionsFragment(sitePermission: SitePermission)
+ UndocumentedPublicFunction:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference$fun setProgressViewShown(isShown: Boolean)
+ UndocumentedPublicFunction:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference$fun setSearchQueryErrorText(err: String)
+ UndocumentedPublicFunction:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference$fun validateEngineNameAndShowError(engineName: String, existingEngines: List<SearchEngine>): Boolean
+ UndocumentedPublicFunction:ManualAddSearchEnginePreference.kt$ManualAddSearchEnginePreference$fun validateSearchQueryAndShowError(searchQuery: String): Boolean
+ UndocumentedPublicFunction:ManualAddSearchEngineSettingsFragment.kt$ManualAddSearchEngineSettingsFragment.Companion$@WorkerThread @JvmStatic fun isValidSearchQueryURL(client: Client, query: String): Boolean
+ UndocumentedPublicFunction:MobileMetricsPingStorage.kt$MobileMetricsPingStorage$fun clearStorage()
+ UndocumentedPublicFunction:MobileMetricsPingStorage.kt$MobileMetricsPingStorage$fun load(): JSONObject?
+ UndocumentedPublicFunction:MobileMetricsPingStorage.kt$MobileMetricsPingStorage$fun save(json: JSONObject)
+ UndocumentedPublicFunction:MobileMetricsPingStorage.kt$MobileMetricsPingStorage$fun shouldStoreMetrics(): Boolean
+ UndocumentedPublicFunction:MozillaSettingsFragment.kt$MozillaSettingsFragment.Companion$fun newInstance(): MozillaSettingsFragment
+ UndocumentedPublicFunction:MultiselectSearchEngineListPreference.kt$MultiselectSearchEngineListPreference$fun atLeastOneEngineChecked(): Boolean
+ UndocumentedPublicFunction:NimbusSetup.kt$fun getNimbusAppName(): String
+ UndocumentedPublicFunction:OnboardingController.kt$OnboardingController$fun handleActivityResultImplementation(activityResult: ActivityResult)
+ UndocumentedPublicFunction:OnboardingController.kt$OnboardingController$fun handleFinishOnBoarding()
+ UndocumentedPublicFunction:OnboardingController.kt$OnboardingController$fun handleGetStartedButtonClicked()
+ UndocumentedPublicFunction:OnboardingController.kt$OnboardingController$fun handleMakeFocusDefaultBrowserButtonClicked(activityResultLauncher: ActivityResultLauncher<Intent>)
+ UndocumentedPublicFunction:OnboardingInteractor.kt$OnboardingInteractor$fun onActivityResultImplementation(activityResult: ActivityResult)
+ UndocumentedPublicFunction:OnboardingInteractor.kt$OnboardingInteractor$fun onFinishOnBoarding()
+ UndocumentedPublicFunction:OnboardingInteractor.kt$OnboardingInteractor$fun onGetStartedButtonClicked()
+ UndocumentedPublicFunction:OnboardingInteractor.kt$OnboardingInteractor$fun onMakeFocusDefaultBrowserButtonClicked(activityResultLauncher: ActivityResultLauncher<Intent>)
+ UndocumentedPublicFunction:Performance.kt$Performance$fun processIntentIfPerformanceTest(bundle: Bundle?, context: Context)
+ UndocumentedPublicFunction:PreferenceSwitch.kt$PreferenceSwitch$fun onClickListener(listener: () -> Unit)
+ UndocumentedPublicFunction:PrivacySecuritySettingsFragment.kt$PrivacySecuritySettingsFragment.Companion$fun newInstance(): PrivacySecuritySettingsFragment
+ UndocumentedPublicFunction:ProfilerMarkerFactProcessor.kt$ProfilerMarkerFactProcessor.Companion$fun create(profilerProvider: () -> Profiler?)
+ UndocumentedPublicFunction:PromoteSearchWidgetDialogCompose.kt$@Suppress("LongMethod") @Composable fun PromoteSearchWidgetDialogCompose( onAddSearchWidgetButtonClick: () -> Unit, onDismiss: () -> Unit, )
+ UndocumentedPublicFunction:RadioButtonPreference.kt$GroupableRadioButton$fun addToRadioGroup(radioButton: GroupableRadioButton)
+ UndocumentedPublicFunction:RadioButtonPreference.kt$GroupableRadioButton$fun updateRadioValue(isChecked: Boolean)
+ UndocumentedPublicFunction:RadioButtonPreference.kt$RadioButtonPreference$fun onClickListener(listener: (() -> Unit))
+ UndocumentedPublicFunction:RadioButtonPreference.kt$fun Iterable<GroupableRadioButton>.uncheckAll()
+ UndocumentedPublicFunction:RemoveSearchEnginesSettingsFragment.kt$RemoveSearchEnginesSettingsFragment.Companion$fun newInstance()
+ UndocumentedPublicFunction:SearchEngineListPreference.kt$SearchEngineListPreference$fun refetchSearchEngines()
+ UndocumentedPublicFunction:SearchOverlay.kt$@OptIn(DelicateCoroutinesApi::class) @Composable fun SearchOverlay( viewModel: SearchSuggestionsViewModel, defaultSearchEngineName: String, onListScrolled: () -> Unit, )
+ UndocumentedPublicFunction:SearchSettingsFragment.kt$SearchSettingsFragment.Companion$fun newInstance(): SearchSettingsFragment
+ UndocumentedPublicFunction:SearchSuggestionsFragment.kt$SearchSuggestionsFragment.Companion$fun create()
+ UndocumentedPublicFunction:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences$fun disableSearchSuggestions()
+ UndocumentedPublicFunction:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences$fun dismissNoSuggestionsMessage()
+ UndocumentedPublicFunction:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences$fun enableSearchSuggestions()
+ UndocumentedPublicFunction:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences$fun hasUserToggledSearchSuggestions(): Boolean
+ UndocumentedPublicFunction:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences$fun searchSuggestionsEnabled(): Boolean
+ UndocumentedPublicFunction:SearchSuggestionsPreferences.kt$SearchSuggestionsPreferences$fun userHasDismissedNoSuggestionsMessage(): Boolean
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun clearAutocompleteSuggestion()
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun clearSearchSuggestion()
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun disableSearchSuggestions()
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun dismissNoSuggestionsMessage()
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun enableSearchSuggestions()
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun refresh()
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun selectSearchSuggestion( suggestion: String, defaultSearchEngineName: String, alwaysSearch: Boolean = false, )
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun setAutocompleteSuggestion(text: String)
+ UndocumentedPublicFunction:SearchSuggestionsViewModel.kt$SearchSuggestionsViewModel$fun setSearchQuery(query: String)
+ UndocumentedPublicFunction:SearchUtils.kt$SearchUtils$fun createSearchUrl(context: Context?, text: String): String
+ UndocumentedPublicFunction:SecretSettingsUnlocker.kt$SecretSettingsUnlocker$fun increment()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun addSearchWidgetInstalled(count: Int)
+ UndocumentedPublicFunction:Settings.kt$Settings$fun createTrackingProtectionPolicy( shouldBlockCookiesValue: String = shouldBlockCookiesValue(), ): EngineSession.TrackingProtectionPolicy
+ UndocumentedPublicFunction:Settings.kt$Settings$fun getAppLaunchCount()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun getClearBrowsingSessions()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun getCurrentCookieBannerOptionFromSharePref(): CookieBannerOption
+ UndocumentedPublicFunction:Settings.kt$Settings$fun getHttpsOnlyMode(): Engine.HttpsOnlyMode
+ UndocumentedPublicFunction:Settings.kt$Settings$fun getTotalBlockedTrackersCount()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun hasAdvertisingBlocked()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun hasAnalyticsBlocked()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun hasRequestedDesktop()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun hasSocialBlocked()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun saveCurrentCookieBannerOptionInSharePref( cookieBannerOption: CookieBannerOption, )
+ UndocumentedPublicFunction:Settings.kt$Settings$fun setDefaultSearchEngineByName(name: String)
+ UndocumentedPublicFunction:Settings.kt$Settings$fun setupSafeBrowsing(engine: Engine, shouldUseSafeBrowsing: Boolean = shouldUseSafeBrowsing())
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldAutocompleteFromCustomDomainList()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldAutocompleteFromShippedDomainList()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockAdTrackers()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockAnalyticTrackers()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockCookiesValue(): String
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockJavaScript(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockOtherTrackers()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockSocialTrackers()
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldBlockWebFonts(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldEnableRemoteDebugging(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldShowSearchSuggestions(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldUseBiometrics(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun shouldUseSecureMode(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun userHasDismissedNoSuggestionsMessage(): Boolean
+ UndocumentedPublicFunction:Settings.kt$Settings$fun userHasToggledSearchSuggestions(): Boolean
+ UndocumentedPublicFunction:SettingsFragment.kt$SettingsFragment.Companion$fun newInstance(): SettingsFragment
+ UndocumentedPublicFunction:SitePermissionOptionsFragment.kt$SitePermissionOptionsFragment.Companion$fun addSitePermission(sitePermission: SitePermission): SitePermissionOptionsFragment
+ UndocumentedPublicFunction:SitePermissionOptionsStorage.kt$SitePermissionOptionsStorage$fun getSitePermissionLabel(sitePermission: SitePermission): String
+ UndocumentedPublicFunction:SitePermissionOptionsStorage.kt$SitePermissionOptionsStorage$fun getSitePermissionsSettingsRules()
+ UndocumentedPublicFunction:SitePermissionOptionsStorage.kt$SitePermissionOptionsStorage$fun isAndroidPermissionGranted(sitePermission: SitePermission): Boolean
+ UndocumentedPublicFunction:SitePermissionOptionsStorage.kt$SitePermissionOptionsStorage$fun isSitePermissionNotBlocked(permissionsList: Array<String>): Boolean
+ UndocumentedPublicFunction:StartupActivityLog.kt$StartupActivityLog$@VisibleForTesting(otherwise = NONE) fun getObserversForTesting()
+ UndocumentedPublicFunction:StartupActivityLog.kt$StartupActivityLog$@VisibleForTesting(otherwise = PRIVATE) fun logEntries(loggerArg: Logger = logger, logLevel: Log.Priority = Log.logLevel)
+ UndocumentedPublicFunction:StartupActivityLog.kt$StartupActivityLog$fun registerInAppOnCreate( application: Application, processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(), )
+ UndocumentedPublicFunction:StartupPathProvider.kt$StartupPathProvider$@VisibleForTesting(otherwise = NONE) fun getTestCallbacks()
+ UndocumentedPublicFunction:StartupPathProvider.kt$StartupPathProvider$fun attachOnActivityOnCreate(lifecycle: Lifecycle, intent: Intent?)
+ UndocumentedPublicFunction:StartupTypeTelemetry.kt$StartupTypeTelemetry$@VisibleForTesting(otherwise = NONE) fun getTestCallbacks()
+ UndocumentedPublicFunction:StartupTypeTelemetry.kt$StartupTypeTelemetry$fun attachOnMainActivityOnCreate(lifecycle: Lifecycle)
+ UndocumentedPublicFunction:StoreLink.kt$StoreLink$fun start()
+ UndocumentedPublicFunction:StudiesViewHolder.kt$StudiesViewHolder.ActiveStudiesViewHolder$fun bindStudy( activeStudy: StudiesListItem.ActiveStudy, removeStudyListener: (StudiesListItem.ActiveStudy) -> Unit, )
+ UndocumentedPublicFunction:StudiesViewHolder.kt$StudiesViewHolder.SectionViewHolder$fun bindSection(section: StudiesListItem.Section)
+ UndocumentedPublicFunction:StudiesViewModel.kt$StudiesViewModel$fun removeStudy(study: StudiesListItem.ActiveStudy)
+ UndocumentedPublicFunction:StudiesViewModel.kt$StudiesViewModel$fun setStudiesState(state: Boolean)
+ UndocumentedPublicFunction:SupportUtils.kt$SupportUtils$fun getGenericSumoURLForTopic(topic: SumoTopic): String
+ UndocumentedPublicFunction:SupportUtils.kt$SupportUtils$fun getSafeBrowsingURL(): String
+ UndocumentedPublicFunction:SupportUtils.kt$SupportUtils$fun openDefaultBrowserSumoPage(context: Context)
+ UndocumentedPublicFunction:SupportUtils.kt$SupportUtils$fun openUrlInCustomTab(activity: FragmentActivity, destinationUrl: String)
+ UndocumentedPublicFunction:TabViewHolder.kt$TabViewHolder$fun bind( tab: TabSessionState, isCurrentSession: Boolean, selectSession: (TabSessionState) -> Unit, closeSession: (TabSessionState) -> Unit, )
+ UndocumentedPublicFunction:Theme.kt$fun Resources.Theme.resolveAttribute(attribute: Int): Int
+ UndocumentedPublicFunction:TopSitesOverlay.kt$@OptIn(DelicateCoroutinesApi::class) @Composable fun TopSitesOverlay(modifier: Modifier = Modifier)
+ UndocumentedPublicFunction:TopSitesOverlay.kt$@OptIn(DelicateCoroutinesApi::class) fun removeTopSite(item: TopSite, components: Components)
+ UndocumentedPublicFunction:TopSitesOverlay.kt$@OptIn(DelicateCoroutinesApi::class) fun renameTopSite(selectedTopSite: TopSite, newTitle: String, components: Components)
+ UndocumentedPublicFunction:TransitionDrawableGroup.kt$TransitionDrawableGroup$fun resetTransition()
+ UndocumentedPublicFunction:TransitionDrawableGroup.kt$TransitionDrawableGroup$fun startTransition(durationMillis: Int)
+ UndocumentedPublicFunction:UrlInputFragment.kt$UrlInputFragment$fun onBackPressed(): Boolean
+ UndocumentedPublicFunction:UrlInputFragment.kt$UrlInputFragment.Companion$@JvmStatic fun createWithTab( tabId: String, ): UrlInputFragment
+ UndocumentedPublicFunction:UrlInputFragment.kt$UrlInputFragment.Companion$@JvmStatic fun createWithoutSession(): UrlInputFragment
+ UndocumentedPublicFunction:ViewUtils.kt$ViewUtils$fun hideKeyboard(view: View?)
+ UndocumentedPublicFunction:ViewUtils.kt$ViewUtils$fun showKeyboard(view: View?)
+ UndocumentedPublicFunction:VisibilityLifeCycleCallback.kt$VisibilityLifeCycleCallback.Companion$fun isInBackground(context: Context): Boolean
+ UnusedPrivateMember:TelemetryMiddleware.kt$TelemetryMiddleware$@Suppress("ComplexMethod") private fun generateOptions(customTabConfig: CustomTabConfig): List<String>
+ UseRequire:FactsProcessor.kt$throw IllegalArgumentException("Fact is not a context menu fact")
+ UtilityClassWithPublicConstructor:AppReviewUtils.kt$AppReviewUtils
+ UtilityClassWithPublicConstructor:IconGenerator.kt$IconGenerator
+
+
diff --git a/mobile/android/focus-android/quality/detekt.yml b/mobile/android/focus-android/quality/detekt.yml
new file mode 100644
index 0000000000..bf5a0b7da2
--- /dev/null
+++ b/mobile/android/focus-android/quality/detekt.yml
@@ -0,0 +1,789 @@
+# Please refer to https://github.com/mozilla-mobile/firefox-android/blob/main/android-components/config/detekt.yml
+# for the source of truth for our detekt configuration.
+
+build:
+ maxIssues: 0
+ excludeCorrectable: false
+ weights:
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
+
+config:
+ validation: true
+ warningsAsErrors: false
+ checkExhaustiveness: false
+ # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
+ excludes: ''
+
+processors:
+ active: true
+ exclude:
+ - 'DetektProgressListener'
+ # - 'KtFileCountProcessor'
+ # - 'PackageCountProcessor'
+ # - 'ClassCountProcessor'
+ # - 'FunctionCountProcessor'
+ # - 'PropertyCountProcessor'
+ # - 'ProjectComplexityProcessor'
+ # - 'ProjectCognitiveComplexityProcessor'
+ # - 'ProjectLLOCProcessor'
+ # - 'ProjectCLOCProcessor'
+ # - 'ProjectLOCProcessor'
+ # - 'ProjectSLOCProcessor'
+ # - 'LicenseHeaderLoaderExtension'
+
+console-reports:
+ active: true
+ exclude:
+ # - 'ProjectStatisticsReport'
+ # - 'ComplexityReport'
+ # - 'NotificationReport'
+ # - 'FindingsReport'
+ # - 'FileBasedFindingsReport'
+ # - 'LiteFindingsReport'
+
+output-reports:
+ active: true
+ exclude:
+ # - 'TxtOutputReport'
+ # - 'XmlOutputReport'
+ # - 'HtmlOutputReport'
+ # - 'MdOutputReport'
+ # - 'SarifOutputReport'
+
+comments:
+ active: true
+ AbsentOrWrongFileLicense:
+ active: true # Enabled in https://bugzilla.mozilla.org/show_bug.cgi?id=1795140
+ licenseTemplateFile: 'license.template'
+ licenseTemplateIsRegex: false
+ CommentOverPrivateFunction:
+ active: false
+ CommentOverPrivateProperty:
+ active: false
+ DeprecatedBlockTag:
+ active: false
+ EndOfSentenceFormat:
+ active: false
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
+ KDocReferencesNonPublicProperty:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ OutdatedDocumentation:
+ active: true # Enabled in https://bugzilla.mozilla.org/show_bug.cgi?id=1848527
+ matchTypeParameters: false # (Default: true) Disabled in https://bugzilla.mozilla.org/show_bug.cgi?id=1848527
+ matchDeclarationsOrder: true
+ allowParamOnConstructorProperties: false
+ UndocumentedPublicClass:
+ active: true # Enabled in https://github.com/mozilla-mobile/android-components/issues/76
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchInNestedClass: true
+ searchInInnerClass: true
+ searchInInnerObject: true
+ searchInInnerInterface: true
+ searchInProtectedClass: false
+ UndocumentedPublicFunction:
+ active: true # Enabled in https://github.com/mozilla-mobile/android-components/issues/76
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedFunction: false
+ UndocumentedPublicProperty:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedProperty: false
+
+complexity:
+ active: true
+ CognitiveComplexMethod:
+ active: false
+ threshold: 15
+ ComplexCondition:
+ active: true
+ threshold: 4
+ ComplexInterface:
+ active: false
+ threshold: 10
+ includeStaticDeclarations: false
+ includePrivateDeclarations: false
+ ignoreOverloaded: false
+ CyclomaticComplexMethod:
+ active: true
+ threshold: 18 # (Default: 15) Increased in https://github.com/mozilla-mobile/android-components/pull/10328
+ ignoreSingleWhenExpression: true # Enabled in https://github.com/mozilla-mobile/android-components/pull/3271
+ ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ nestingFunctions:
+ - 'also'
+ - 'apply'
+ - 'forEach'
+ - 'isNotNull'
+ - 'ifNull'
+ - 'let'
+ - 'run'
+ - 'use'
+ - 'with'
+ LabeledExpression:
+ active: false
+ ignoredLabels: []
+ LargeClass:
+ active: true
+ threshold: 600
+ LongMethod:
+ active: true
+ threshold: 75 # (Default: 60) Increased in https://github.com/mozilla-mobile/android-components/issues/6350
+ LongParameterList:
+ active: true
+ functionThreshold: 8 # (Default: 6) Increased in https://bugzilla.mozilla.org/show_bug.cgi?id=1872996
+ constructorThreshold: 7
+ ignoreDefaultParameters: true # Enabled in https://github.com/mozilla-mobile/android-components/issues/10835
+ ignoreDataClasses: true
+ ignoreAnnotatedParameter: []
+ MethodOverloading:
+ active: false
+ threshold: 6
+ NamedArguments:
+ active: false
+ threshold: 3
+ ignoreArgumentsMatchingNames: false
+ NestedBlockDepth:
+ active: true
+ threshold: 4
+ NestedScopeFunctions:
+ active: false
+ threshold: 1
+ functions:
+ - 'kotlin.apply'
+ - 'kotlin.run'
+ - 'kotlin.with'
+ - 'kotlin.let'
+ - 'kotlin.also'
+ ReplaceSafeCallChainWithRun:
+ active: false
+ StringLiteralDuplication:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ threshold: 3
+ ignoreAnnotation: true
+ excludeStringsWithLessThan5Characters: true
+ ignoreStringsRegex: '$^'
+ TooManyFunctions:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ thresholdInFiles: 26 # (Default: 11) Increased in https://github.com/mozilla-mobile/android-components/pull/9927
+ thresholdInClasses: 26 # (Default: 11) Increased in https://github.com/mozilla-mobile/android-components/pull/9927
+ thresholdInInterfaces: 26 # (Default: 11) Increased in https://github.com/mozilla-mobile/android-components/pull/9927
+ thresholdInObjects: 26 # (Default: 11) Increased in https://github.com/mozilla-mobile/android-components/pull/9927
+ thresholdInEnums: 11
+ ignoreDeprecated: false
+ ignorePrivate: false
+ ignoreOverridden: false
+
+coroutines:
+ active: true
+ GlobalCoroutineUsage:
+ active: false
+ InjectDispatcher:
+ active: false # (Default: true)
+ dispatcherNames:
+ - 'IO'
+ - 'Default'
+ - 'Unconfined'
+ RedundantSuspendModifier:
+ active: false # (Default: true)
+ SleepInsteadOfDelay:
+ active: false # (Default: true)
+ SuspendFunSwallowedCancellation:
+ active: false
+ SuspendFunWithCoroutineScopeReceiver:
+ active: false
+ SuspendFunWithFlowReturnType:
+ active: false # (Default: true)
+
+empty-blocks:
+ active: true
+ EmptyCatchBlock:
+ active: true
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ EmptyClassBlock:
+ active: true
+ EmptyDefaultConstructor:
+ active: true
+ EmptyDoWhileBlock:
+ active: true
+ EmptyElseBlock:
+ active: true
+ EmptyFinallyBlock:
+ active: true
+ EmptyForBlock:
+ active: true
+ EmptyFunctionBlock:
+ active: true
+ ignoreOverridden: false
+ EmptyIfBlock:
+ active: true
+ EmptyInitBlock:
+ active: true
+ EmptyKtFile:
+ active: true
+ EmptySecondaryConstructor:
+ active: true
+ EmptyTryBlock:
+ active: true
+ EmptyWhenBlock:
+ active: true
+ EmptyWhileBlock:
+ active: true
+
+exceptions:
+ active: true
+ ExceptionRaisedInUnexpectedLocation:
+ active: false # (Default: true)
+ methodNames:
+ - 'equals'
+ - 'finalize'
+ - 'hashCode'
+ - 'toString'
+ InstanceOfCheckForException:
+ active: false # (Default: true)
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ NotImplementedDeclaration:
+ active: false
+ ObjectExtendsThrowable:
+ active: false
+ PrintStackTrace:
+ active: true
+ RethrowCaughtException:
+ active: false # (Default: true)
+ ReturnFromFinally:
+ active: true
+ ignoreLabeled: false
+ SwallowedException:
+ active: false # (Default: true)
+ ignoredExceptionTypes:
+ - 'InterruptedException'
+ - 'MalformedURLException'
+ - 'NumberFormatException'
+ - 'ParseException'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ ThrowingExceptionFromFinally:
+ active: true
+ ThrowingExceptionInMain:
+ active: false
+ ThrowingExceptionsWithoutMessageOrCause:
+ active: false # (Default: true)
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptions:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Exception'
+ - 'IllegalArgumentException'
+ - 'IllegalMonitorStateException'
+ - 'IllegalStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ ThrowingNewInstanceOfSameException:
+ active: true
+ TooGenericExceptionCaught:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptionNames:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Error'
+ - 'Exception'
+ - 'IllegalMonitorStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ TooGenericExceptionThrown:
+ active: true
+ exceptionNames:
+ - 'Error'
+ - 'Exception'
+ - 'RuntimeException'
+ - 'Throwable'
+
+naming:
+ active: true
+ BooleanPropertyNaming:
+ active: false
+ allowedPattern: '^(is|has|are)'
+ ClassNaming:
+ active: true
+ classPattern: '[A-Z][a-zA-Z0-9]*'
+ ConstructorParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ privateParameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ EnumNaming:
+ active: true
+ enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
+ ForbiddenClassName:
+ active: false
+ forbiddenName: []
+ FunctionMaxLength:
+ active: false
+ maximumFunctionNameLength: 30
+ FunctionMinLength:
+ active: false
+ minimumFunctionNameLength: 3
+ FunctionNaming:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ functionPattern: '[a-z][a-zA-Z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreAnnotated: ['Composable'] # Configuration for Compose https://github.com/mozilla-mobile/android-components/issues/11866
+ FunctionParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ InvalidPackageDeclaration:
+ active: false # (Default: true)
+ rootPackage: ''
+ requireRootInDeclaration: false
+ LambdaParameterNaming:
+ active: false
+ parameterPattern: '[a-z][A-Za-z0-9]*|_'
+ MatchingDeclarationName:
+ active: true
+ mustBeFirst: true
+ MemberNameEqualsClassName:
+ active: false # (Default: true)
+ ignoreOverridden: true
+ NoNameShadowing:
+ active: false # (Default: true)
+ NonBooleanPropertyPrefixedWithIs:
+ active: false
+ ObjectPropertyNaming:
+ active: true
+ constantPattern: '[A-Za-z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
+ PackageNaming:
+ active: true
+ packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
+ TopLevelPropertyNaming:
+ active: true
+ constantPattern: '[A-Z][_A-Z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
+ VariableMaxLength:
+ active: false
+ maximumVariableNameLength: 64
+ VariableMinLength:
+ active: false
+ minimumVariableNameLength: 1
+ VariableNaming:
+ active: true
+ variablePattern: '[a-z][A-Za-z0-9]*'
+ privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+
+performance:
+ active: true
+ ArrayPrimitive:
+ active: true
+ CouldBeSequence:
+ active: false
+ threshold: 3
+ ForEachOnRange:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ SpreadOperator:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnnecessaryPartOfBinaryExpression:
+ active: false
+ UnnecessaryTemporaryInstantiation:
+ active: true
+
+potential-bugs:
+ active: true
+ AvoidReferentialEquality:
+ active: false # (Default: true)
+ forbiddenTypePatterns:
+ - 'kotlin.String'
+ CastNullableToNonNullableType:
+ active: false
+ CastToNullableType:
+ active: false
+ Deprecation:
+ active: false
+ DontDowncastCollectionTypes:
+ active: false
+ DoubleMutabilityForCollection:
+ active: true
+ mutableTypes:
+ - 'kotlin.collections.MutableList'
+ - 'kotlin.collections.MutableMap'
+ - 'kotlin.collections.MutableSet'
+ - 'java.util.ArrayList'
+ - 'java.util.LinkedHashSet'
+ - 'java.util.HashSet'
+ - 'java.util.LinkedHashMap'
+ - 'java.util.HashMap'
+ ElseCaseInsteadOfExhaustiveWhen:
+ active: false
+ ignoredSubjectTypes: []
+ EqualsAlwaysReturnsTrueOrFalse:
+ active: true
+ EqualsWithHashCodeExist:
+ active: true
+ ExitOutsideMain:
+ active: false
+ ExplicitGarbageCollectionCall:
+ active: true
+ HasPlatformType:
+ active: false # (Default: true)
+ IgnoredReturnValue:
+ active: false # (Default: true)
+ restrictToConfig: true
+ returnValueAnnotations:
+ - 'CheckResult'
+ - '*.CheckResult'
+ - 'CheckReturnValue'
+ - '*.CheckReturnValue'
+ ignoreReturnValueAnnotations:
+ - 'CanIgnoreReturnValue'
+ - '*.CanIgnoreReturnValue'
+ returnValueTypes:
+ - 'kotlin.sequences.Sequence'
+ - 'kotlinx.coroutines.flow.*Flow'
+ - 'java.util.stream.*Stream'
+ ignoreFunctionCall: []
+ ImplicitDefaultLocale:
+ active: false # (Default: true)
+ ImplicitUnitReturnType:
+ active: false
+ allowExplicitReturnType: true
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
+ IteratorNotThrowingNoSuchElementException:
+ active: true
+ LateinitUsage:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ ignoreOnClassesPattern: ''
+ MapGetWithNotNullAssertionOperator:
+ active: false # (Default: true)
+ MissingPackageDeclaration:
+ active: false
+ excludes: ['**/*.kts']
+ NullCheckOnMutableProperty:
+ active: false
+ NullableToStringCall:
+ active: false
+ PropertyUsedBeforeDeclaration:
+ active: false
+ UnconditionalJumpStatementInLoop:
+ active: false
+ UnnecessaryNotNullCheck:
+ active: false
+ UnnecessaryNotNullOperator:
+ active: true
+ UnnecessarySafeCall:
+ active: true
+ UnreachableCatchBlock:
+ active: false # (Default: true)
+ UnreachableCode:
+ active: true
+ UnsafeCallOnNullableType:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnsafeCast:
+ active: true
+ UnusedUnaryOperator:
+ active: false # (Default: true)
+ UselessPostfixExpression:
+ active: false # (Default: true)
+ WrongEqualsTypeParameter:
+ active: true
+
+style:
+ active: true
+ AlsoCouldBeApply:
+ active: false
+ BracesOnIfStatements:
+ active: true # Enabled in https://github.com/mozilla-mobile/android-components/issues/1015
+ singleLine: 'consistent' # (Default: never)
+ multiLine: 'always'
+ BracesOnWhenStatements:
+ active: false
+ singleLine: 'necessary'
+ multiLine: 'consistent'
+ CanBeNonNullable:
+ active: false
+ CascadingCallWrapping:
+ active: false
+ includeElvis: true
+ ClassOrdering:
+ active: false
+ CollapsibleIfStatements:
+ active: true # Enabled in https://github.com/mozilla-mobile/android-components/pull/78
+ DataClassContainsFunctions:
+ active: false
+ conversionFunctionPrefix:
+ - 'to'
+ allowOperators: false
+ DataClassShouldBeImmutable:
+ active: false
+ DestructuringDeclarationWithTooManyEntries:
+ active: false # (Default: true)
+ maxDestructuringEntries: 3
+ DoubleNegativeLambda:
+ active: false
+ negativeFunctions:
+ - reason: 'Use `takeIf` instead.'
+ value: 'takeUnless'
+ - reason: 'Use `all` instead.'
+ value: 'none'
+ negativeFunctionNameParts:
+ - 'not'
+ - 'non'
+ EqualsNullCall:
+ active: true
+ EqualsOnSignatureLine:
+ active: false
+ ExplicitCollectionElementAccessMethod:
+ active: false
+ ExplicitItLambdaParameter:
+ active: false # (Default: true)
+ ExpressionBodySyntax:
+ active: false
+ includeLineWrapping: false
+ ForbiddenAnnotation:
+ active: false
+ annotations:
+ - reason: 'it is a java annotation. Use `Suppress` instead.'
+ value: 'java.lang.SuppressWarnings'
+ - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
+ value: 'java.lang.Deprecated'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
+ value: 'java.lang.annotation.Documented'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
+ value: 'java.lang.annotation.Target'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
+ value: 'java.lang.annotation.Retention'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
+ value: 'java.lang.annotation.Repeatable'
+ - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265'
+ value: 'java.lang.annotation.Inherited'
+ ForbiddenComment:
+ active: true
+ comments:
+ - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
+ value: 'FIXME:'
+ - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
+ value: 'STOPSHIP:'
+ - reason: 'Forbidden TODO todo marker in comment, please do the changes.'
+ value: 'TODO:'
+ allowedPatterns: ''
+ ForbiddenImport:
+ active: false
+ imports: []
+ forbiddenPatterns: ''
+ ForbiddenMethodCall:
+ active: false
+ methods:
+ - reason: 'print does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.print'
+ - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.println'
+ ForbiddenSuppress:
+ active: false
+ rules: []
+ ForbiddenVoid:
+ active: false # (Default: true)
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
+ FunctionOnlyReturningConstant:
+ active: true
+ ignoreOverridableFunction: true
+ ignoreActualFunction: true
+ excludedFunctions: []
+ LoopWithTooManyJumpStatements:
+ active: false # (Default: true)
+ maxJumpCount: 1
+ MagicNumber:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
+ ignoreNumbers:
+ - '-1'
+ - '0'
+ - '1'
+ - '2'
+ ignoreHashCodeFunction: true
+ ignorePropertyDeclaration: true # Enabled for Compose in https://github.com/mozilla-mobile/android-components/issues/11864
+ ignoreLocalVariableDeclaration: false
+ ignoreConstantDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreAnnotation: false
+ ignoreNamedArgument: true
+ ignoreEnums: false
+ ignoreRanges: false
+ ignoreExtensionFunctions: true
+ MandatoryBracesLoops:
+ active: false
+ MaxChainedCallsOnSameLine:
+ active: false
+ maxChainedCalls: 5
+ MaxLineLength:
+ active: true
+ maxLineLength: 120
+ excludePackageStatements: true
+ excludeImportStatements: true
+ excludeCommentStatements: false
+ excludeRawStrings: true
+ MayBeConst:
+ active: true
+ ModifierOrder:
+ active: true
+ MultilineLambdaItParameter:
+ active: false
+ MultilineRawStringIndentation:
+ active: false
+ indentSize: 4
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ NestedClassesVisibility:
+ active: true
+ NewLineAtEndOfFile:
+ active: true
+ NoTabs:
+ active: true # Enabled in https://github.com/mozilla-mobile/android-components/pull/78
+ NullableBooleanCheck:
+ active: false
+ ObjectLiteralToLambda:
+ active: false # (Default: true)
+ OptionalAbstractKeyword:
+ active: true
+ OptionalUnit:
+ active: false
+ PreferToOverPairSyntax:
+ active: false
+ ProtectedMemberInFinalClass:
+ active: true
+ RedundantExplicitType:
+ active: false
+ RedundantHigherOrderMapUsage:
+ active: false # (Default: true)
+ RedundantVisibilityModifierRule:
+ active: false
+ ReturnCount:
+ active: true
+ max: 3 # (Default: 2) Increased in https://github.com/mozilla-mobile/android-components/issues/3
+ excludedFunctions:
+ - 'equals'
+ excludeLabeled: false
+ excludeReturnFromLambda: true
+ excludeGuardClauses: false
+ SafeCast:
+ active: true
+ SerialVersionUIDInSerializableClass:
+ active: true
+ SpacingBetweenPackageAndImports:
+ active: true # Enabled in https://github.com/mozilla-mobile/android-components/pull/78
+ StringShouldBeRawString:
+ active: false
+ maxEscapedCharacterCount: 2
+ ignoredCharacters: []
+ ThrowsCount:
+ active: true
+ max: 2
+ excludeGuardClauses: false
+ TrailingWhitespace:
+ active: false
+ TrimMultilineRawString:
+ active: false
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ UnderscoresInNumericLiterals:
+ active: false
+ acceptableLength: 4
+ allowNonStandardGrouping: false
+ UnnecessaryAbstractClass:
+ active: true
+ UnnecessaryAnnotationUseSiteTarget:
+ active: false
+ UnnecessaryApply:
+ active: true
+ UnnecessaryBackticks:
+ active: false
+ UnnecessaryBracesAroundTrailingLambda:
+ active: false
+ UnnecessaryFilter:
+ active: false # (Default: true)
+ UnnecessaryInheritance:
+ active: true
+ UnnecessaryInnerClass:
+ active: false
+ UnnecessaryLet:
+ active: false
+ UnnecessaryParentheses:
+ active: false
+ allowForUnclearPrecedence: false
+ UntilInsteadOfRangeTo:
+ active: false
+ UnusedImports:
+ active: false
+ UnusedParameter:
+ active: true
+ allowedNames: 'ignored|expected'
+ UnusedPrivateClass:
+ active: true
+ UnusedPrivateMember:
+ active: true
+ allowedNames: ''
+ ignoreAnnotated: ['Composable'] # Configuration for Compose https://github.com/mozilla-mobile/android-components/issues/11866
+ UnusedPrivateProperty:
+ active: true
+ allowedNames: '_|ignored|expected|serialVersionUID'
+ UseAnyOrNoneInsteadOfFind:
+ active: false # (Default: true)
+ UseArrayLiteralsInAnnotations:
+ active: false # (Default: true)
+ UseCheckNotNull:
+ active: true
+ UseCheckOrError:
+ active: false # (Default: true)
+ UseDataClass:
+ active: false
+ allowVars: false
+ UseEmptyCounterpart:
+ active: false
+ UseIfEmptyOrIfBlank:
+ active: false
+ UseIfInsteadOfWhen:
+ active: false
+ ignoreWhenContainingVariableDeclaration: false
+ UseIsNullOrEmpty:
+ active: false # (Default: true)
+ UseLet:
+ active: false
+ UseOrEmpty:
+ active: true
+ UseRequire:
+ active: true
+ UseRequireNotNull:
+ active: true
+ UseSumOfInsteadOfFlatMapSize:
+ active: false
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
+ VarCouldBeVal:
+ active: true
+ ignoreLateinitVar: false
+ WildcardImport:
+ active: true
+ excludeImports:
+ - 'java.util.*'
diff --git a/mobile/android/focus-android/quality/license.template b/mobile/android/focus-android/quality/license.template
new file mode 100644
index 0000000000..e0032240a4
--- /dev/null
+++ b/mobile/android/focus-android/quality/license.template
@@ -0,0 +1,3 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
diff --git a/mobile/android/focus-android/quality/pmd-rules.xml b/mobile/android/focus-android/quality/pmd-rules.xml
new file mode 100644
index 0000000000..16054cf1c5
--- /dev/null
+++ b/mobile/android/focus-android/quality/pmd-rules.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Focus Android Ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/focus-android/quality/pre-push-recommended.sh b/mobile/android/focus-android/quality/pre-push-recommended.sh
new file mode 100755
index 0000000000..cbc1f6acd4
--- /dev/null
+++ b/mobile/android/focus-android/quality/pre-push-recommended.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# We recommend you run this as a pre-push hook: to reduce
+# review turn-around time, we want all pushes to run tests
+# locally. Using this hook will guarantee your hook gets
+# updated as the repository changes.
+#
+# This hook tries to run as much as possible without taking
+# too long.
+#
+# You can use it by running this command from the project root:
+# `ln -s ../../quality/pre-push-recommended.sh .git/hooks/pre-push`
+
+# Descriptions for each gradle task below can be found in the
+# output of `./gradlew tasks`.
+./gradlew -q \
+ ktlint \
+ detekt \
+ assembleFocusDebugAndroidTest \
+ testFocusDebugUnitTest
+
+# Tasks omitted because they take a long time to run:
+# - unit test on all variants
+# - UI tests
+# - lint (compiles all variants)
diff --git a/mobile/android/focus-android/quality/spotbugs-exclude.xml b/mobile/android/focus-android/quality/spotbugs-exclude.xml
new file mode 100644
index 0000000000..90a6da87eb
--- /dev/null
+++ b/mobile/android/focus-android/quality/spotbugs-exclude.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/focus-android/settings.gradle b/mobile/android/focus-android/settings.gradle
new file mode 100644
index 0000000000..0f5151cfe6
--- /dev/null
+++ b/mobile/android/focus-android/settings.gradle
@@ -0,0 +1,136 @@
+/* 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/. */
+
+pluginManagement {
+ apply from: file('../gradle/mozconfig.gradle')
+
+ repositories {
+ gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
+ maven {
+ url repository
+ if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
+ allowInsecureProtocol = true
+ }
+ }
+ }
+ }
+
+ includeBuild("../android-components/plugins/publicsuffixlist")
+ includeBuild("../android-components/plugins/dependencies")
+ includeBuild("../android-components/plugins/config")
+ includeBuild("./plugins/focusdependencies")
+}
+
+plugins {
+ id "mozac.ConfigPlugin"
+ id 'mozac.DependenciesPlugin'
+ id 'FocusDependenciesPlugin'
+}
+
+ext.topsrcdir = rootProject.projectDir.absolutePath.minus("mobile/android/focus-android")
+
+apply from: file('../shared-settings.gradle')
+
+include ':app'
+
+gradle.projectsLoaded { ->
+ // Disables A-C tests and lint when building Focus.
+ gradle.allprojects { project ->
+ if (project.projectDir.absolutePath.contains("/android-components/")) {
+ project.tasks.withType(Test).configureEach {
+ enabled = false
+ }
+ project.tasks.configureEach { task ->
+ if (task.name.contains("lint")) {
+ task.enabled = false
+ }
+ }
+ }
+
+ def appServicesSrcDir = null
+ if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
+ appServicesSrcDir = gradle.getProperty('localProperties.autoPublish.application-services.dir')
+ } else if (gradle.hasProperty('localProperties.branchBuild.application-services.dir')) {
+ appServicesSrcDir = gradle.getProperty('localProperties.branchBuild.application-services.dir')
+ }
+ if (appServicesSrcDir) {
+ if (appServicesSrcDir.startsWith("/")) {
+ apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
+ } else {
+ apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
+ }
+ }
+ }
+}
+
+def log(message) {
+ logger.lifecycle("[settings] ${message}")
+}
+
+def runCmd(cmd, workingDir, successMessage, captureStdout=true) {
+ def proc = cmd.execute(null, new File(workingDir))
+ def standardOutput = captureStdout ? new ByteArrayOutputStream() : System.out
+ proc.consumeProcessOutput(standardOutput, System.err)
+ proc.waitFor()
+
+ if (proc.exitValue() != 0) {
+ throw new GradleException("Process '${cmd}' finished with non-zero exit value ${proc.exitValue()}");
+ } else {
+ log(successMessage)
+ }
+ return captureStdout ? standardOutput : null
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Local development enhancements
+//////////////////////////////////////////////////////////////////////////
+
+Properties localProperties = null
+String settingAppServicesPath = "autoPublish.application-services.dir"
+String settingGleanPath = "autoPublish.glean.dir"
+
+if (file('local.properties').canRead()) {
+ localProperties = new Properties()
+ localProperties.load(file('local.properties').newDataInputStream())
+ log('Loaded local.properties')
+} else {
+ log('Missing local.properties; see https://github.com/mozilla-mobile/focus-android#localproperties-helpers for instructions.')
+}
+
+if (localProperties != null) {
+ localProperties.each { prop ->
+ gradle.ext.set("localProperties.${prop.key}", prop.value)
+ }
+
+ String appServicesLocalPath = localProperties.getProperty(settingAppServicesPath)
+
+ if (appServicesLocalPath != null) {
+ log("Enabling automatic publication of application-services from: $appServicesLocalPath")
+ // Windows can't execute .py files directly, so we assume a "manually installed" python,
+ // which comes with a "py" launcher and respects the shebang line to specify the version.
+ def publishAppServicesCmd = [];
+ if (System.properties['os.name'].toLowerCase().contains('windows')) {
+ publishAppServicesCmd << "py";
+ }
+ publishAppServicesCmd << "./automation/publish_to_maven_local_if_modified.py";
+ runCmd(publishAppServicesCmd, appServicesLocalPath, "Published application-services for local development.", false)
+ } else {
+ log("Disabled auto-publication of application-services. Enable it by settings '$settingAppServicesPath' in local.properties")
+ }
+
+ String gleanLocalPath = localProperties.getProperty(settingGleanPath)
+
+ if (gleanLocalPath != null) {
+ log("Enabling automatic publication of Glean from: $gleanLocalPath")
+ // As above, hacks to execute .py files on Windows.
+ def publishGleanCmd = [];
+ if (System.properties['os.name'].toLowerCase().contains('windows')) {
+ publishGleanCmd << "py";
+ }
+ publishGleanCmd << "./build-scripts/publish_to_maven_local_if_modified.py";
+ runCmd(publishGleanCmd, gleanLocalPath, "Published Glean for local development.", false)
+ } else {
+ log("Disabled auto-publication of Glean. Enable it by settings '$settingGleanPath' in local.properties")
+ }
+}
diff --git a/mobile/android/focus-android/tools/data_renewal_generate.py b/mobile/android/focus-android/tools/data_renewal_generate.py
new file mode 100755
index 0000000000..43dba38541
--- /dev/null
+++ b/mobile/android/focus-android/tools/data_renewal_generate.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+# 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 https://mozilla.org/MPL/2.0/.
+
+"""
+A script to help generate telemetry renewal csv and request template.
+This script also modifies metrics.yaml to mark soon to expired telemetry entries.
+"""
+
+import csv
+import json
+import os
+import sys
+
+import yaml
+from yaml.loader import FullLoader
+
+METRICS_FILENAME = "../app/metrics.yaml"
+NEW_METRICS_FILENAME = "../app/metrics_new.yaml"
+GLEAN_DICTIONARY_PREFIX = (
+ "https://dictionary.telemetry.mozilla.org/apps/focus_android/metrics/"
+)
+
+# This is to make sure we only write headers for the csv file once
+write_header = True
+# The number of soon to expired telemetry detected
+total_count = 0
+
+USAGE = """usage: ./{script_name} future_fenix_version_number"""
+
+# list of values that we care about
+_KEY_FILTER = [
+ "type",
+ "description",
+ "bugs",
+ "data_reviews",
+ "expires",
+]
+
+
+def response(last_key, content, expire_version, writer, renewal):
+ global write_header
+ global total_count
+ for key, value in content.items():
+ if (key == "$schema") or (key == "no_lint"):
+ continue
+ if key == "disabled":
+ continue
+
+ if ("expires" in value) and (
+ (value["expires"] == "never") or (not value["expires"] <= expire_version)
+ ):
+ continue
+
+ if key == "type":
+ remove_keys = []
+ for key in content.keys():
+ if key not in _KEY_FILTER:
+ remove_keys.append(key)
+
+ for key in remove_keys:
+ content.pop(key)
+
+ content["bugs"] = content["bugs"][0]
+ content["data_reviews"] = content["data_reviews"][0]
+ total_count += 1
+
+ # name of the telemtry
+ dictionary_url = GLEAN_DICTIONARY_PREFIX + last_key.lstrip(".").replace(
+ ".", "_"
+ )
+ result = {
+ "#": total_count,
+ "name": last_key.lstrip("."),
+ "glean dictionary": dictionary_url,
+ }
+ result.update(content)
+
+ # add columns for product to fille out, these should always be added at the end
+ result.update({"keep(Y/N)": ""})
+ result.update({"new expiry version": ""})
+ result.update({"reason to extend": ""})
+
+ # output data-renewal request template
+ if write_header:
+ header = result.keys()
+ writer.writerow(header)
+ write_header = False
+ renewal.write("# Request for Data Collection Renewal\n")
+ renewal.write("### Renew for 1 year\n")
+ renewal.write("Total: TBD\n")
+ renewal.write("———\n")
+
+ writer.writerow(result.values())
+
+ renewal.write("`" + last_key.lstrip(".") + "`:\n")
+ renewal.write(
+ "1) Provide a link to the initial Data Collection Review Request for this collection.\n"
+ )
+ renewal.write(" - " + content["data_reviews"] + "\n")
+ renewal.write("\n")
+ renewal.write("2) When will this collection now expire?\n")
+ renewal.write(" - TBD\n")
+ renewal.write("\n")
+ renewal.write("3) Why was the initial period of collection insufficient?\n")
+ renewal.write(" - TBD\n")
+ renewal.write("\n")
+ renewal.write("———\n")
+ return
+
+ if type(value) is dict:
+ response(last_key + "." + key, value, expire_version, writer, renewal)
+
+
+with open(METRICS_FILENAME, "r") as f:
+ try:
+ arg1 = sys.argv[1]
+ except Exception:
+ print("usage is to include argument of the form `100`")
+ quit()
+
+ # parse metrics.yaml to json
+ write_header = True
+ data = yaml.load(f, Loader=FullLoader)
+ json_data = json.dumps(data)
+ content = json.loads(str(json_data))
+ csv_filename = arg1 + "_expiry_list.csv"
+ renewal_filename = arg1 + "_renewal_request.txt"
+ current_version = int(arg1)
+
+ # remove files created by last run if exists
+ if os.path.exists(csv_filename):
+ print("remove old csv file")
+ os.remove(csv_filename)
+
+ # remove files created by last run if exists
+ if os.path.exists(renewal_filename):
+ print("remove old renewal request template file")
+ os.remove(renewal_filename)
+
+ # remove files created by last run if exists
+ if os.path.exists(NEW_METRICS_FILENAME):
+ print("remove old metrics yaml file")
+ os.remove(NEW_METRICS_FILENAME)
+
+ data_file = open(csv_filename, "w")
+ csv_writer = csv.writer(data_file)
+ renewal_file = open(renewal_filename, "w")
+
+ response("", content, current_version, csv_writer, renewal_file)
+ renewal_file.close()
+ print("Completed")
+ print("Total count: " + str(total_count))
+
+ # Go through the metrics.yaml file to mark expired telemetry
+ verify_count = 0
+ f.seek(0, 0)
+ data = f.readlines()
+ with open(NEW_METRICS_FILENAME, "w") as f2:
+ for line in data:
+ if line.lstrip(" ").startswith("expires: ") and not (
+ line.lstrip(" ").startswith("expires: never")
+ ):
+ start_pos = len("expires: ")
+ version = int(line.lstrip(" ")[start_pos:])
+ if version <= current_version:
+ verify_count += 1
+ f2.writelines(
+ line.rstrip("\n")
+ + " /* TODO <"
+ + str(verify_count)
+ + "> require renewal */\n"
+ )
+ else:
+ f2.writelines(line)
+ else:
+ f2.writelines(line)
+ f2.close()
+
+ print("\n==============================")
+ if total_count != verify_count:
+ print("!!! Count check failed !!!")
+ else:
+ print("Count check passed")
+ print("==============================")
+
+ os.remove(METRICS_FILENAME)
+ os.rename(NEW_METRICS_FILENAME, METRICS_FILENAME)
diff --git a/mobile/android/focus-android/tools/data_renewal_request.py b/mobile/android/focus-android/tools/data_renewal_request.py
new file mode 100755
index 0000000000..5f14292b0d
--- /dev/null
+++ b/mobile/android/focus-android/tools/data_renewal_request.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# 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 https://mozilla.org/MPL/2.0/.
+
+"""
+A script to help generate a data review request comment. Once the CSV has been filled by product,
+copy the filled version into the tools directory and run this script from there to generate a filled
+renewal request data review comment.
+"""
+
+import csv
+import sys
+
+try:
+ version = sys.argv[1]
+except Exception:
+ print("usage is to include arguments of the form ")
+ quit()
+
+expiry_filename = version + "_expiry_list.csv"
+filled_renewal_filename = version + "_filled_renewal_request.txt"
+
+csv_reader = csv.DictReader(open(expiry_filename, "r"))
+output_string = ""
+total_count = 0
+updated_version = int(version) + 13
+for row in csv_reader:
+ if row["keep(Y/N)"] == "n":
+ continue
+ total_count += 1
+ output_string += f'` {row["name"]}`\n'
+ output_string += "1) Provide a link to the initial Data Collection Review Request for this collection.\n"
+ output_string += f' - {eval(row["data_reviews"])[0]}\n'
+ output_string += "\n"
+ output_string += "2) When will this collection now expire?\n"
+ if len(row["new expiry version"]) == 0:
+ output_string += f" - {updated_version}\n"
+ else:
+ output_string += f' - {row["new expiry version"]}\n'
+
+ output_string += "\n"
+ output_string += "3) Why was the initial period of collection insufficient?\n"
+ output_string += f' - {row["reason to extend"]}\n'
+ output_string += "\n"
+ output_string += "———\n"
+
+header = "# Request for Data Collection Renewal\n"
+header += "### Renew for 1 year\n"
+header += f"Total: {total_count}\n"
+header += "———\n\n"
+
+with open(filled_renewal_filename, "w+") as out:
+ out.write(header + output_string)
+ out.close()
diff --git a/mobile/android/focus-android/tools/docker/Dockerfile b/mobile/android/focus-android/tools/docker/Dockerfile
new file mode 100644
index 0000000000..f0a1be21a7
--- /dev/null
+++ b/mobile/android/focus-android/tools/docker/Dockerfile
@@ -0,0 +1,78 @@
+# 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/.
+
+# Inspired by:
+# https://hub.docker.com/r/runmymind/docker-android-sdk/~/dockerfile/
+
+FROM ubuntu:22.04
+
+MAINTAINER Sebastian Kaspari "skaspari@mozilla.com"
+
+# -- System -----------------------------------------------------------------------------
+
+ENV GRADLE_OPTS='-Xmx4096m -Dorg.gradle.daemon=false' \
+ LANG='en_US.UTF-8' \
+ TERM='dumb' \
+ JAVA17PATH="/usr/lib/jvm/java-17-openjdk-amd64/bin/:$PATH"
+
+RUN apt-get update -qq \
+ # We need to install tzdata before all of the other packages. Otherwise it will show an interactive dialog
+ # which we cannot navigate while building the Docker image.
+ && apt-get install -y tzdata \
+ && apt-get install -y openjdk-17-jdk \
+ git \
+ curl \
+ python3 \
+ python-pip3 \
+ locales \
+ unzip \
+ mercurial \
+ && apt-get clean
+
+# Today's Fastlane depends on a newer Ruby version than Ubuntu 17.10 has, so since
+# fastlane is only used for screenshots (afaik) just skip it.
+#RUN gem install fastlane
+
+RUN locale-gen en_US.UTF-8
+
+# -- Android SDK ------------------------------------------------------------------------
+
+RUN cd /opt && curl --location --retry 5 --output android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip \
+ && unzip -d /opt/android-sdk-linux android-sdk.zip \
+ && rm -f android-sdk.zip
+
+ENV ANDROID_SDK_HOME /opt/android-sdk-linux
+ENV ANDROID_HOME /opt/android-sdk-linux
+
+RUN yes | PATH=$JAVA17PATH "${ANDROID_SDK_HOME}/cmdline-tools/bin/sdkmanager" --licenses
+
+# -- Project setup ----------------------------------------------------------------------
+
+WORKDIR /opt
+
+# Checkout source code
+RUN git clone https://github.com/mozilla-mobile/focus-android.git
+
+# Build project and run gradle tasks once to pull all dependencies
+WORKDIR /opt/focus-android
+RUN ./gradlew assembleFocusDebug \
+ && ./gradlew testFocusDebugUnitTest \
+ && ./gradlew detekt \
+ && ./gradlew ktlint \
+ && ./gradlew clean
+
+# -- Post setup -------------------------------------------------------------------------
+
+# Install taskcluster python library (used by decision tasks)
+# 5.0.0 is still incompatible with taskclusterProxy, meaning no decision task is able
+# to schedule the rest of the Taskcluster tasks. Please upgrade to taskcluster>=5 once
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1460015 is fixed
+RUN pip install 'taskcluster>=4,<5'
+
+# Install Google Cloud SDK for using Firebase Test Lab
+RUN cd /opt && curl --location --retry 5 --output gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-331.0.0-linux-x86_64.tar.gz \
+ && tar -xvf /opt/gcloud.tar.gz \
+ && rm -f gcloud.tar.gz \
+ && /opt/google-cloud-sdk/install.sh --quiet \
+ && /opt/google-cloud-sdk/bin/gcloud --quiet components update
diff --git a/mobile/android/focus-android/tools/docker/licenses/android-sdk-license b/mobile/android/focus-android/tools/docker/licenses/android-sdk-license
new file mode 100644
index 0000000000..ff1da21494
--- /dev/null
+++ b/mobile/android/focus-android/tools/docker/licenses/android-sdk-license
@@ -0,0 +1,2 @@
+
+8933bad161af4178b1185d1a37fbf41ea5269c55
diff --git a/mobile/android/focus-android/tools/docker/licenses/android-sdk-preview-license b/mobile/android/focus-android/tools/docker/licenses/android-sdk-preview-license
new file mode 100644
index 0000000000..74069f8f02
--- /dev/null
+++ b/mobile/android/focus-android/tools/docker/licenses/android-sdk-preview-license
@@ -0,0 +1,2 @@
+
+84831b9409646a918e30573bab4c9c91346d8abd
diff --git a/mobile/android/focus-android/tools/gradle/versionCode.gradle b/mobile/android/focus-android/tools/gradle/versionCode.gradle
new file mode 100644
index 0000000000..73a4de1189
--- /dev/null
+++ b/mobile/android/focus-android/tools/gradle/versionCode.gradle
@@ -0,0 +1,45 @@
+// 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/.
+
+import java.text.SimpleDateFormat
+
+// This gradle scripts generates a "unique" version code for our release versions.
+//
+// The result of the version code depends on the timezone. We assume that this script will only be used
+// for release versions and running on our build servers with a fixed timezone.
+//
+// The version code is composed like: yDDDHHmm
+// * y = Double digit year, with 16 substracted: 2017 -> 17 -> 1
+// * DDD = Day of the year, pad with zeros if needed: September 6th -> 249
+// * HH = Hour in day (00-23)
+// * mm = Minute in hour
+//
+// For September 6th, 2017, 9:41 am this will generate the versionCode: 12490941 (1-249-09-41).
+//
+// Note that we only use this generated version code for builds we want to distribute. For local
+// debug builds we use a fixed versionCode to not mess with the caching mechanism of the build
+// system.
+
+ext {
+ def base = "3"
+ def today = new Date()
+
+ // We use the current year (double digit) and substract 16. We first released Focus in
+ // 2017 so this value will start counting at 1 and increment by one every year.
+ def year = String.valueOf((new SimpleDateFormat("yy").format(today) as int) - 16)
+
+ // We use the day in the Year (e.g. 248) as opposed to month + day (0510) because it's one digit shorter.
+ // If needed we pad with zeros (e.g. 25 -> 025)
+ def day = String.format("%03d", (new SimpleDateFormat("D").format(today) as int))
+
+ // We append the hour in day (24h) and minute in hour (7:26 pm -> 1926). We do not append
+ // seconds. This assumes that we do not need to build multiple release(!) builds the same
+ // minute.
+ def time = new SimpleDateFormat("HHmm").format(today)
+
+ generatedVersionCode = (base + year + day + time) as int
+
+ println("Generated versionCode: $generatedVersionCode")
+ println()
+}
diff --git a/mobile/android/focus-android/tools/update-glean-tags.py b/mobile/android/focus-android/tools/update-glean-tags.py
new file mode 100755
index 0000000000..8e77a8241b
--- /dev/null
+++ b/mobile/android/focus-android/tools/update-glean-tags.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+"""
+Scrapes GitHub labels for Focus and generates a set of glean tags for use in metrics
+
+See https://mozilla.github.io/glean/book/reference/yaml/tags.html
+"""
+import urllib
+from pathlib import Path
+
+import requests
+import yaml
+
+LICENSE_HEADER = """# 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/.
+"""
+
+GENERATED_HEADER = """
+### This file was AUTOMATICALLY GENERATED by `./tools/update-glean-tags.py`
+### DO NOT edit it by hand.
+
+# Disable line-length rule because the links in the descriptions can be long
+# yamllint disable rule:line-length
+"""
+
+TAGS_FILENAME = (Path(__file__).parent / "../app/tags.yaml").resolve()
+
+labels = []
+page = 1
+while True:
+ more_labels = requests.get(
+ f"https://api.github.com/repos/mozilla-mobile/focus-android/labels?per_page=100&page={page}"
+ ).json()
+ if not more_labels:
+ break
+ labels += more_labels
+ page += 1
+
+tags = {"$schema": "moz://mozilla.org/schemas/glean/tags/1-0-0"}
+for label in labels:
+ if label["name"].startswith("Feature:"):
+ abbreviated_label = label["name"].replace("Feature:", "")
+ url = (
+ "https://github.com/mozilla-mobile/focus-android/issues?q="
+ + urllib.parse.quote_plus(f"label:{label['name']}")
+ )
+ label_description = (
+ (label["description"].strip() + ". ") if len(label["description"]) else ""
+ )
+ tags[abbreviated_label] = {
+ "description": f"{label_description}Corresponds to the [{label['name']}]({url}) label on GitHub."
+ }
+
+open(TAGS_FILENAME, "w").write(
+ "{}\n{}\n\n".format(LICENSE_HEADER, GENERATED_HEADER)
+ + yaml.dump(tags, width=78, explicit_start=True)
+)
--
cgit v1.2.3