From b289c87444c34d400d650ad299635f19908d4e76 Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Mon, 20 Nov 2023 22:25:13 +0000 Subject: [PATCH] Preparation for Autofill enabled by default (#3695) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/488551667048375/1205737758794661/f ### Description Preparatory work to enable us to ship autofill enabled by default. ℹ️ **Notes on reviewing this PR** - All PRs in the stack have been individually reviewed/approved already. - This PR therefore doesn't require a line-by-line review of the code changes (though feel free!) and is more about a sense-check that things are working as expected. **Note on remote config integration** - Remote config isn't live to actually make it enabled by default yet - But after this PR lands the app will be ready to do so when we choose (soon) ### Steps to test this PR Perform a general smoke test of the main Autofill features, as detailed below. #### Saving a login - [x] Enable autofill from overflow->Logins (will be enabled already if on `internal` build) - [x] Visit https://fill.dev/form/login-simple - [x] Enter a username and password (>= 4 characters) and submit - [x] Verify dialog looks good, and matches the **after** picture like the one below - [x] Save the login #### Filling a login - [x] Revisit https://fill.dev/form/login-simple - [x] Verify dialog to autofill looks good, and matches the **after** picture like the one below #### Password generation - [x] Visit https://fill.dev/form/registration-username - [x] Verify dialog to autofill looks good, and matches the **after** picture like the one below ### UI changes --------- Co-authored-by: Marcos Holgado Co-authored-by: root --- .../app/browser/BrowserTabFragment.kt | 1 - .../com/duckduckgo/app/pixels/AppPixelName.kt | 2 - .../app/settings/SettingsViewModel.kt | 1 - app/src/main/res/values-bg/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-et/strings.xml | 2 +- app/src/main/res/values-fi/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-hr/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-lt/strings.xml | 2 +- app/src/main/res/values-lv/strings.xml | 2 +- app/src/main/res/values-nb/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-sl/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- .../app/settings/SettingsViewModelTest.kt | 3 +- .../autofill/api/AutofillFeature.kt | 7 + .../impl/AutofillJavascriptInterface.kt | 14 +- .../AutofillDeduplicationBestMatchFinder.kt | 97 ++++++++++ .../AutofillDeduplicationMatchTypeDetector.kt | 62 +++++++ ...DeduplicationUsernameAndPasswordMatcher.kt | 35 ++++ .../impl/deduper/AutofillLoginDeduplicator.kt | 54 ++++++ .../RealLoginSorterForDeduplication.kt | 53 ++++++ .../autofill/impl/di/AutofillModule.kt | 21 ++- .../impl/pixel/AutofillPixelInterceptor.kt | 123 +++++++++++++ .../autofill/impl/pixel/AutofillPixelNames.kt | 7 + .../SystemAutofillServiceSuppressor.kt | 43 +++++ .../management/AutofillManagementActivity.kt | 9 + .../management/AutofillSettingsViewModel.kt | 21 +++ ...AutofillSavingCredentialsDialogFragment.kt | 21 --- .../AutofillDisablingDeclineCounter.kt | 2 +- ...AutofillSelectCredentialsDialogFragment.kt | 17 -- ...datingExistingCredentialsDialogFragment.kt | 21 --- ...management_credential_list_empty_state.xml | 17 +- ...tent_autofill_generate_password_dialog.xml | 172 ++++++++++-------- .../content_autofill_save_new_credentials.xml | 163 ++++++++--------- ...nt_autofill_select_credentials_tooltip.xml | 96 +++++----- ...t_autofill_update_existing_credentials.xml | 137 +++++++------- .../dialog_email_protection_choose_email.xml | 95 +++++----- ...og_email_protection_in_context_sign_up.xml | 164 ++++++++--------- ...fragment_autofill_management_list_mode.xml | 3 +- .../res/values-bg/strings-autofill-impl.xml | 22 +-- .../res/values-cs/strings-autofill-impl.xml | 22 +-- .../res/values-da/strings-autofill-impl.xml | 22 +-- .../res/values-de/strings-autofill-impl.xml | 22 +-- .../res/values-el/strings-autofill-impl.xml | 22 +-- .../res/values-es/strings-autofill-impl.xml | 22 +-- .../res/values-et/strings-autofill-impl.xml | 22 +-- .../res/values-fi/strings-autofill-impl.xml | 22 +-- .../res/values-fr/strings-autofill-impl.xml | 22 +-- .../res/values-hr/strings-autofill-impl.xml | 22 +-- .../res/values-hu/strings-autofill-impl.xml | 22 +-- .../res/values-it/strings-autofill-impl.xml | 14 +- .../res/values-lt/strings-autofill-impl.xml | 22 +-- .../res/values-lv/strings-autofill-impl.xml | 22 +-- .../res/values-nb/strings-autofill-impl.xml | 22 +-- .../res/values-nl/strings-autofill-impl.xml | 22 +-- .../res/values-pl/strings-autofill-impl.xml | 22 +-- .../res/values-pt/strings-autofill-impl.xml | 22 +-- .../res/values-ro/strings-autofill-impl.xml | 22 +-- .../res/values-ru/strings-autofill-impl.xml | 22 +-- .../res/values-sk/strings-autofill-impl.xml | 22 +-- .../res/values-sl/strings-autofill-impl.xml | 22 +-- .../res/values-sv/strings-autofill-impl.xml | 22 +-- .../res/values-tr/strings-autofill-impl.xml | 22 +-- .../res/values/autofill-impl-dimensions.xml | 1 + .../main/res/values/strings-autofill-impl.xml | 22 +-- .../src/main/res/values/styles.xml | 14 ++ .../impl/AutofillCapabilityCheckerImplTest.kt | 79 +------- ...tofillStoredBackJavascriptInterfaceTest.kt | 10 + .../RealAutofillLoginDeduplicatorTest.kt | 165 +++++++++++++++++ .../RealAutofillMatchTypeDetectorTest.kt | 53 ++++++ ...ealLoginDeduplicatorBestMatchFinderTest.kt | 88 +++++++++ ...uplicatorUsernameAndPasswordMatcherTest.kt | 70 +++++++ .../RealLoginSorterForDeduplicationTest.kt | 80 ++++++++ ...rotectionInContextAvailabilityRulesTest.kt | 8 +- .../pixel/AutofillPixelInterceptorTest.kt | 57 ++++++ .../AutofillSettingsViewModelTest.kt | 50 +++++ .../AutofillDisablingDeclineCounterTest.kt | 2 +- .../store/RealAutofillPrefsStoreTest.kt | 70 +++++++ .../RealAutofillDefaultStateDeciderTest.kt | 70 +++++++ .../toggles/api/toggle/AutofillTestFeature.kt | 42 +++++ .../src/main/AndroidManifest.xml | 2 +- .../AutofillInternalSettingsActivity.kt | 152 +++++++++++++++- .../activity_autofill_internal_settings.xml | 167 +++++++++++++---- .../src/main/res/values/donottranslate.xml | 15 ++ autofill/autofill-store/build.gradle | 1 + .../autofill/store/AutofillPrefsStore.kt | 70 +++++-- .../feature/AutofillDefaultStateDecider.kt | 51 ++++++ 102 files changed, 2453 insertions(+), 917 deletions(-) create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationBestMatchFinder.kt create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationMatchTypeDetector.kt create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationUsernameAndPasswordMatcher.kt create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillLoginDeduplicator.kt create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplication.kt create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptor.kt create mode 100644 autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/systemautofill/SystemAutofillServiceSuppressor.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillLoginDeduplicatorTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillMatchTypeDetectorTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorBestMatchFinderTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorUsernameAndPasswordMatcherTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplicationTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptorTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/RealAutofillPrefsStoreTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/feature/RealAutofillDefaultStateDeciderTest.kt create mode 100644 autofill/autofill-impl/src/test/java/com/duckduckgo/feature/toggles/api/toggle/AutofillTestFeature.kt create mode 100644 autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/feature/AutofillDefaultStateDecider.kt diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index 809c50d1b50f..30762490c54a 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -3068,7 +3068,6 @@ class BrowserTabFragment : viewModel.onPrintSelected() } onMenuItemClicked(menuBinding.autofillMenuItem) { - pixel.fire(AppPixelName.MENU_ACTION_AUTOFILL_PRESSED) viewModel.onAutofillMenuSelected() } } diff --git a/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt b/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt index 71f4761e3fac..636b3789c847 100644 --- a/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt +++ b/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt @@ -103,7 +103,6 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName { SETTINGS_APP_LINKS_ALWAYS_SELECTED("ms_app_links_always_setting_selected"), SETTINGS_APP_LINKS_NEVER_SELECTED("ms_app_links_never_setting_selected"), SETTINGS_ADD_HOME_SCREEN_WIDGET_CLICKED("ms_add_home_screen_widget_clicked"), - SETTINGS_AUTOFILL_MANAGEMENT_OPENED("m_autofill_settings_opened"), SETTINGS_DEFAULT_BROWSER_PRESSED("ms_default_browser_pressed"), SETTINGS_PRIVATE_SEARCH_PRESSED("ms_private_search_setting_pressed"), SETTINGS_WEB_TRACKING_PROTECTION_PRESSED("ms_web_tracking_protection_setting_pressed"), @@ -204,7 +203,6 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName { MENU_ACTION_SETTINGS_PRESSED("m_nav_s_p"), MENU_ACTION_APP_LINKS_OPEN_PRESSED("m_nav_app_links_open_menu_item_pressed"), MENU_ACTION_DOWNLOADS_PRESSED("m_nav_downloads_menu_item_pressed"), - MENU_ACTION_AUTOFILL_PRESSED("m_nav_autofill_menu_item_pressed"), FIREPROOF_WEBSITE_ADDED("m_fw_a"), FIREPROOF_WEBSITE_REMOVE("m_fw_r"), diff --git a/app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt b/app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt index af590408bfee..56ced0f56d59 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt @@ -256,7 +256,6 @@ class SettingsViewModel @Inject constructor( fun onAutofillSettingsClick() { viewModelScope.launch { command.send(Command.LaunchAutofillSettings) } - pixel.fire(SETTINGS_AUTOFILL_MANAGEMENT_OPENED) } fun onAccessibilitySettingClicked() { diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 9f0d24949547..dcb51973c8f2 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -150,7 +150,7 @@ Активирано по подразбиране Настройки Разрешения - Автоматично попълване на данни за вход + Данни за вход Синхронизиране и архивиране Fire Button diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 591420d16725..fa405b10a8ec 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -150,7 +150,7 @@ Ve výchozím nastavení povoleno Nastavení Oprávnění - Automatické vyplňování přihlašovacích údajů + Přihlášení Synchronizace a zálohování Fire Button diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 97c45a541494..5351fb03a4d0 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -150,7 +150,7 @@ Aktiveret som standard Indstillinger Tilladelser - Automatisk udfyldning ved login + Logins Synkronisering og sikkerhedskopiering Fire Button diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c1ec9410309a..7a6c14c44bc6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -150,7 +150,7 @@ Standardmäßig aktiviert Einstellungen Berechtigungen - Anmeldedaten automatisch ausfüllen + Anmeldungen Synchronisieren und sichern Fire Button diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3a0199155589..5f320a8409cb 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -150,7 +150,7 @@ Ενεργοποιημένο βάσει προεπιλογής Ρυθμίσεις Δικαιώματα - Συνδέσεις αυτόματης συμπλήρωσης + Συνδέσεις Συγχρονισμός και δημιουργία αντιγράφων ασφαλείας Κουμπί Φωτιά diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index feb956246b42..7dc1a707c7c0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -150,7 +150,7 @@ Habilitado de forma predeterminada Ajustes Permisos - Autocompletar inicios de sesión + Inicios de sesión Sincronización y copia de seguridad Fire Button diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index fcbb17cbcda7..82a78f4306c8 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -150,7 +150,7 @@ Vaikimisi sisse lülitatud Sätted Load - Sisesta sisselogimisandmed automaatselt + Sisselogimine Sünkroonimine ja varundamine Fire Button diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index fc3d27bffce5..771efea37234 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -150,7 +150,7 @@ Oletusarvoisesti käytössä Asetukset Käyttöoikeudet - Täytä kirjautumistiedot automaattisesti + Kirjautumistiedot Synkronoi ja varmuuskopioi Fire-painike diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1ec402c90cb1..c15255ee06f9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -150,7 +150,7 @@ Activation par défaut Paramètres Autorisations - Saisie automatique des identifiants + Identifiants Synchronisation et sauvegarde Fire Button diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 3d0f3d088632..0b9b58b2bf0b 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -150,7 +150,7 @@ Omogućeno prema zadanim postavkama Postavke Dozvole - Automatsko popunjavanje prijave + Prijave Sinkronizacija i sigurnosno kopiranje Fire Button diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 1701d00c3fc3..860029dddd5f 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -150,7 +150,7 @@ Alapértelmezés szerint engedélyezve Beállítások Engedélyek - Bejelentkezési adatok automatikus kitöltése + Bejelentkezések Szinkronizálás és biztonsági mentés Tűz gomb diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4b0e42030e3b..df516e90e267 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -150,7 +150,7 @@ Abilitato per impostazione predefinita Impostazioni Autorizzazioni - Compilazione automatica dei dati di accesso + Accessi Sincronizzazione e backup Fire Button diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 338c93d451ae..12192f399882 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -150,7 +150,7 @@ Įjungta pagal numatytuosius nustatymus Nustatymai Leidimai - Automatiškai užpildyti prisijungimus + Prisijungimai Sinchronizuoti ir kurti atsarginę kopiją Mygtukas „Fire“ diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 642dd8fcfedc..c38d36443db3 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -150,7 +150,7 @@ Iespējots pēc noklusējuma Iestatījumi Atļaujas - Automātiski aizpildīt pieteikšanās datus + Pieteikšanās dati Sinhronizācija un dublēšana Fire Button diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 6348e8c489bc..1b620fe04b60 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -150,7 +150,7 @@ Aktivert som standard Innstillinger Tillatelser - Fyll inn pålogginger automatisk + Pålogginger Synkronisering og sikkerhetskopiering Fire Button diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c940562ee4ab..f6a41dcce94d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -150,7 +150,7 @@ Standaard ingeschakeld Instellingen Toestemmingen - Login met automatisch invullen + Aanmeldgegevens Synchronisatie en back-up Fire Button diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index fa28bfe54bd7..800965b6c17f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -150,7 +150,7 @@ Domyślnie włączone Ustawienia Uprawnienia - Autouzupełnianie logowania + Dane logowania Synchronizacja i kopia zapasowa Przycisk zabezpieczenia diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 4f15a1824e4e..092967139805 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -150,7 +150,7 @@ Ativado por predefinição Definições Permissões - Preenchimento automático de inícios de sessão + Inícios de sessão Sincronização e cópia de segurança Botão de proteção diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 0c23d8068167..c782f41841ad 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -150,7 +150,7 @@ Activat în mod implicit Setări Permisiuni - Completare automată la autentificare + Conectări Sincronizare și copiere de rezervă Butonul Foc diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3d2821ff1c5c..392801d9df75 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -150,7 +150,7 @@ Включено по умолчанию Настройки Разрешения - Автозаполнение логинов + Логины Синхронизация и резервное копирование Кнопка «Тревога» diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 8f0d3afa7406..467c1e7a9e96 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -150,7 +150,7 @@ Predvolene povolené Nastavenia Oprávnenia - Automatické vyplňovanie prihlasovacích údajov + Prihlasovacie údaje Synchronizácia a zálohovanie Tlačidlo Fire diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 6f3b1e6407ee..fb0e1f59bdc3 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -150,7 +150,7 @@ Privzeto omogočeno Nastavitve Dovoljenja - Samodejno izpolnjevanje obrazcev za prijavo + Prijave Sinhronizacija in varnostno kopiranje Gumb Fire Button diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5038631eeebd..ed3df90f4910 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -150,7 +150,7 @@ Aktiverat som standard Inställningar Behörigheter - Fyll i inloggningar automatiskt + Inloggningar Synkronisering och säkerhetskopiering Fire Button diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f843cd36912d..c24000599a26 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -150,7 +150,7 @@ Varsayılan olarak etkinleştirildi Ayarlar İzinler - Girişleri Otomatik Doldur + Giriş bilgileri Senkronizasyon ve Yedekleme Fire Button diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5f8f6b0faa0..7f1b8954001c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,7 +149,7 @@ Enabled by default Settings Permissions - Autofill Logins + Logins Sync & Backup Fire Button diff --git a/app/src/test/java/com/duckduckgo/app/settings/SettingsViewModelTest.kt b/app/src/test/java/com/duckduckgo/app/settings/SettingsViewModelTest.kt index 64c86bb5fe7b..bbbeb7bd3b98 100644 --- a/app/src/test/java/com/duckduckgo/app/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/com/duckduckgo/app/settings/SettingsViewModelTest.kt @@ -377,12 +377,11 @@ class SettingsViewModelTest { } @Test - fun whenAutofillSettingsClickThenEmitCommandLaunchAutofillSettingsAndPixelFired() = runTest { + fun whenAutofillSettingsClickThenEmitCommandLaunchAutofillSettings() = runTest { testee.commands().test { testee.onAutofillSettingsClick() assertEquals(Command.LaunchAutofillSettings, awaitItem()) - verify(mockPixel).fire(AppPixelName.SETTINGS_AUTOFILL_MANAGEMENT_OPENED) cancelAndConsumeRemainingEvents() } diff --git a/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt b/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt index 7e65a0514e06..fbf54f3ddffe 100644 --- a/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt +++ b/autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt @@ -56,4 +56,11 @@ interface AutofillFeature { */ @Toggle.DefaultValue(false) fun canAccessCredentialManagement(): Toggle + + /** + * @return `true` when the remote config has the global "onByDefault" autofill sub-feature flag enabled + * If the remote feature is not present defaults to `false` + */ + @Toggle.DefaultValue(false) + fun onByDefault(): Toggle } diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/AutofillJavascriptInterface.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/AutofillJavascriptInterface.kt index eb27613a2214..d6380052393c 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/AutofillJavascriptInterface.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/AutofillJavascriptInterface.kt @@ -28,6 +28,7 @@ import com.duckduckgo.autofill.api.domain.app.LoginTriggerType import com.duckduckgo.autofill.api.email.EmailManager import com.duckduckgo.autofill.api.passwordgeneration.AutomaticSavedLoginsMonitor import com.duckduckgo.autofill.api.store.AutofillStore +import com.duckduckgo.autofill.impl.deduper.AutofillLoginDeduplicator import com.duckduckgo.autofill.impl.domain.javascript.JavascriptCredentials import com.duckduckgo.autofill.impl.email.incontext.availability.EmailProtectionInContextRecentInstallChecker import com.duckduckgo.autofill.impl.email.incontext.store.EmailProtectionInContextDataStore @@ -43,6 +44,7 @@ import com.duckduckgo.autofill.impl.jsbridge.request.SupportedAutofillTriggerTyp import com.duckduckgo.autofill.impl.jsbridge.request.SupportedAutofillTriggerType.USER_INITIATED import com.duckduckgo.autofill.impl.jsbridge.response.AutofillResponseWriter import com.duckduckgo.autofill.impl.sharedcreds.ShareableCredentials +import com.duckduckgo.autofill.impl.systemautofill.SystemAutofillServiceSuppressor import com.duckduckgo.autofill.impl.ui.credential.passwordgeneration.Actions import com.duckduckgo.autofill.impl.ui.credential.passwordgeneration.Actions.DeleteAutoLogin import com.duckduckgo.autofill.impl.ui.credential.passwordgeneration.Actions.DiscardAutoLoginId @@ -108,6 +110,8 @@ class AutofillStoredBackJavascriptInterface @Inject constructor( private val emailManager: EmailManager, private val inContextDataStore: EmailProtectionInContextDataStore, private val recentInstallChecker: EmailProtectionInContextRecentInstallChecker, + private val loginDeduplicator: AutofillLoginDeduplicator, + private val systemAutofillServiceSuppressor: SystemAutofillServiceSuppressor, ) : AutofillJavascriptInterface { override var callback: Callback? = null @@ -209,10 +213,13 @@ class AutofillStoredBackJavascriptInterface @Inject constructor( val credentials = filterRequestedSubtypes(request, matches) - if (credentials.isEmpty()) { + val dedupedCredentials = loginDeduplicator.deduplicate(url, credentials) + Timber.v("Original autofill credentials list size: %d, after de-duping: %d", credentials.size, dedupedCredentials.size) + + if (dedupedCredentials.isEmpty()) { callback?.noCredentialsAvailable(url) } else { - callback?.onCredentialsAvailableToInject(url, credentials, triggerType) + callback?.onCredentialsAvailableToInject(url, dedupedCredentials, triggerType) } } @@ -243,6 +250,9 @@ class AutofillStoredBackJavascriptInterface @Inject constructor( @JavascriptInterface fun storeFormData(data: String) { + // important to call suppressor as soon as possible + systemAutofillServiceSuppressor.suppressAutofill(webView) + Timber.i("storeFormData called, credentials provided to be persisted") storeFormDataJob += coroutineScope.launch(dispatcherProvider.io()) { diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationBestMatchFinder.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationBestMatchFinder.kt new file mode 100644 index 000000000000..b81f37a30948 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationBestMatchFinder.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.autofill.impl.deduper.AutofillDeduplicationMatchTypeDetector.MatchType.NotAMatch +import com.duckduckgo.autofill.impl.deduper.AutofillDeduplicationMatchTypeDetector.MatchType.PartialMatch +import com.duckduckgo.autofill.impl.deduper.AutofillDeduplicationMatchTypeDetector.MatchType.PerfectMatch +import com.duckduckgo.autofill.impl.urlmatcher.AutofillUrlMatcher +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface AutofillDeduplicationBestMatchFinder { + + fun findBestMatch( + originalUrl: String, + logins: List, + ): LoginCredentials? +} + +@ContributesBinding(AppScope::class) +class RealAutofillDeduplicationBestMatchFinder @Inject constructor( + private val urlMatcher: AutofillUrlMatcher, + private val matchTypeDetector: AutofillDeduplicationMatchTypeDetector, +) : AutofillDeduplicationBestMatchFinder { + + override fun findBestMatch( + originalUrl: String, + logins: List, + ): LoginCredentials? { + // perfect matches are those where the subdomain and e-tld+1 match + val perfectMatches = mutableListOf() + + // partial matches are those where only e-tld+1 matches + val partialMatches = mutableListOf() + + // non-matches are those where neither subdomain nor e-tld+1 match + val nonMatches = mutableListOf() + + categoriseEachLogin(logins, originalUrl, perfectMatches, partialMatches, nonMatches) + + if (perfectMatches.isEmpty() && partialMatches.isEmpty() && nonMatches.isEmpty()) { + return null + } + + return if (perfectMatches.isNotEmpty()) { + bestPerfectMatch(perfectMatches) + } else if (partialMatches.isNotEmpty()) { + bestPartialMatch(partialMatches) + } else { + bestNonMatch(nonMatches) + } + } + + private fun categoriseEachLogin( + logins: List, + originalUrl: String, + perfectMatches: MutableList, + partialMatches: MutableList, + nonMatches: MutableList, + ) { + logins.forEach { + when (matchTypeDetector.detectMatchType(originalUrl, it)) { + PerfectMatch -> perfectMatches.add(it) + PartialMatch -> partialMatches.add(it) + NotAMatch -> nonMatches.add(it) + } + } + } + + private fun bestPerfectMatch(perfectMatches: List): LoginCredentials { + return perfectMatches.sortedWith(AutofillDeduplicationLoginComparator()).first() + } + + private fun bestPartialMatch(partialMatches: MutableList): LoginCredentials { + return partialMatches.sortedWith(AutofillDeduplicationLoginComparator()).first() + } + + private fun bestNonMatch(nonMatches: MutableList): LoginCredentials { + return nonMatches.sortedWith(AutofillDeduplicationLoginComparator()).first() + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationMatchTypeDetector.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationMatchTypeDetector.kt new file mode 100644 index 000000000000..74161b0d0c83 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationMatchTypeDetector.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.autofill.impl.deduper.AutofillDeduplicationMatchTypeDetector.MatchType +import com.duckduckgo.autofill.impl.urlmatcher.AutofillUrlMatcher +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface AutofillDeduplicationMatchTypeDetector { + + fun detectMatchType( + originalUrl: String, + login: LoginCredentials, + ): MatchType + + sealed interface MatchType { + object PerfectMatch : MatchType + object PartialMatch : MatchType + object NotAMatch : MatchType + } +} + +@ContributesBinding(AppScope::class) +class RealAutofillDeduplicationMatchTypeDetector @Inject constructor( + private val urlMatcher: AutofillUrlMatcher, +) : AutofillDeduplicationMatchTypeDetector { + + override fun detectMatchType( + originalUrl: String, + login: LoginCredentials, + ): MatchType { + val visitedSiteParts = urlMatcher.extractUrlPartsForAutofill(originalUrl) + val savedSiteParts = urlMatcher.extractUrlPartsForAutofill(login.domain) + + if (!urlMatcher.matchingForAutofill(visitedSiteParts, savedSiteParts)) { + return MatchType.NotAMatch + } + + return if (visitedSiteParts.subdomain == savedSiteParts.subdomain) { + MatchType.PerfectMatch + } else { + MatchType.PartialMatch + } + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationUsernameAndPasswordMatcher.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationUsernameAndPasswordMatcher.kt new file mode 100644 index 000000000000..35de6a2e1cf6 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillDeduplicationUsernameAndPasswordMatcher.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface AutofillDeduplicationUsernameAndPasswordMatcher { + + fun groupDuplicateCredentials(logins: List): Map, List> +} + +@ContributesBinding(AppScope::class) +class RealAutofillDeduplicationUsernameAndPasswordMatcher @Inject constructor() : AutofillDeduplicationUsernameAndPasswordMatcher { + + override fun groupDuplicateCredentials(logins: List): Map, List> { + return logins.groupBy { it.username to it.password } + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillLoginDeduplicator.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillLoginDeduplicator.kt new file mode 100644 index 000000000000..b47d1abc296d --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/AutofillLoginDeduplicator.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface AutofillLoginDeduplicator { + + fun deduplicate( + originalUrl: String, + logins: List, + ): List +} + +@ContributesBinding(AppScope::class) +class RealAutofillLoginDeduplicator @Inject constructor( + private val usernamePasswordMatcher: AutofillDeduplicationUsernameAndPasswordMatcher, + private val bestMatchFinder: AutofillDeduplicationBestMatchFinder, +) : AutofillLoginDeduplicator { + + override fun deduplicate( + originalUrl: String, + logins: List, + ): List { + val dedupedLogins = mutableListOf() + + val groups = usernamePasswordMatcher.groupDuplicateCredentials(logins) + groups.forEach { + val bestMatchForGroup = bestMatchFinder.findBestMatch(originalUrl, it.value) + if (bestMatchForGroup != null) { + dedupedLogins.add(bestMatchForGroup) + } + } + + return dedupedLogins + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplication.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplication.kt new file mode 100644 index 000000000000..0cdb53c5bb38 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplication.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials + +class AutofillDeduplicationLoginComparator : Comparator { + override fun compare( + o1: LoginCredentials, + o2: LoginCredentials, + ): Int { + val lastModifiedComparison = compareLastModified(o1.lastUpdatedMillis, o2.lastUpdatedMillis) + if (lastModifiedComparison != 0) return lastModifiedComparison + + // last updated matches, fallback to domain + return compareDomains(o1.domain, o2.domain) + } + + private fun compareLastModified( + lastModified1: Long?, + lastModified2: Long?, + ): Int { + if (lastModified1 == null && lastModified2 == null) return 0 + + if (lastModified1 == null) return -1 + if (lastModified2 == null) return 1 + return lastModified2.compareTo(lastModified1) + } + + private fun compareDomains( + domain1: String?, + domain2: String?, + ): Int { + if (domain1 == null && domain2 == null) return 0 + if (domain1 == null) return -1 + if (domain2 == null) return 1 + return domain1.compareTo(domain2) + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/di/AutofillModule.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/di/AutofillModule.kt index bfa3f7117ffc..65a97f35b3b9 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/di/AutofillModule.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/di/AutofillModule.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.room.Room import com.duckduckgo.anvil.annotations.ContributesPluginPoint import com.duckduckgo.app.di.AppCoroutineScope +import com.duckduckgo.autofill.api.AutofillFeature import com.duckduckgo.autofill.api.AutofillFragmentResultsPlugin import com.duckduckgo.autofill.api.InternalTestUserChecker import com.duckduckgo.autofill.impl.encoding.UrlUnicodeNormalizer @@ -34,12 +35,15 @@ import com.duckduckgo.autofill.store.LastUpdatedTimeProvider import com.duckduckgo.autofill.store.RealAutofillPrefsStore import com.duckduckgo.autofill.store.RealInternalTestUserStore import com.duckduckgo.autofill.store.RealLastUpdatedTimeProvider +import com.duckduckgo.autofill.store.feature.AutofillDefaultStateDecider import com.duckduckgo.autofill.store.feature.AutofillFeatureRepository +import com.duckduckgo.autofill.store.feature.RealAutofillDefaultStateDecider import com.duckduckgo.autofill.store.feature.RealAutofillFeatureRepository import com.duckduckgo.autofill.store.feature.email.incontext.ALL_MIGRATIONS as EmailInContextMigrations import com.duckduckgo.autofill.store.feature.email.incontext.EmailProtectionInContextDatabase import com.duckduckgo.autofill.store.feature.email.incontext.EmailProtectionInContextFeatureRepository import com.duckduckgo.autofill.store.feature.email.incontext.RealEmailProtectionInContextFeatureRepository +import com.duckduckgo.browser.api.UserBrowserProperties import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.AppScope import com.squareup.anvil.annotations.ContributesTo @@ -63,9 +67,22 @@ class AutofillModule { @Provides fun provideAutofillPrefsStore( context: Context, - internalTestUserChecker: InternalTestUserChecker, + autofillDefaultStateDecider: AutofillDefaultStateDecider, ): AutofillPrefsStore { - return RealAutofillPrefsStore(context, internalTestUserChecker) + return RealAutofillPrefsStore(context, autofillDefaultStateDecider) + } + + @Provides + fun providerAutofillDefaultStateProvider( + userBrowserProperties: UserBrowserProperties, + autofillFeature: AutofillFeature, + internalTestUserChecker: InternalTestUserChecker, + ): AutofillDefaultStateDecider { + return RealAutofillDefaultStateDecider( + userBrowserProperties = userBrowserProperties, + autofillFeature = autofillFeature, + internalTestUserChecker = internalTestUserChecker, + ) } @Provides diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptor.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptor.kt new file mode 100644 index 000000000000..144d247f01c7 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptor.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.pixel + +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_DISABLE +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_KEEP_USING +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_PASSWORD_GENERATION_ACCEPTED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_PASSWORD_GENERATION_PROMPT_DISMISSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_PASSWORD_GENERATION_PROMPT_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_LOGIN_PROMPT_DISMISSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_LOGIN_PROMPT_SAVED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_LOGIN_PROMPT_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_PASSWORD_PROMPT_DISMISSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_PASSWORD_PROMPT_SAVED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_PASSWORD_PROMPT_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SELECT_LOGIN_AUTOPROMPT_DISMISSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SELECT_LOGIN_AUTOPROMPT_SELECTED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SELECT_LOGIN_AUTOPROMPT_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SELECT_LOGIN_PROMPT_DISMISSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SELECT_LOGIN_PROMPT_SELECTED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SELECT_LOGIN_PROMPT_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.MENU_ACTION_AUTOFILL_PRESSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.SETTINGS_AUTOFILL_MANAGEMENT_OPENED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelParameters.AUTOFILL_DEFAULT_STATE +import com.duckduckgo.autofill.store.AutofillPrefsStore +import com.duckduckgo.common.utils.plugins.pixel.PixelInterceptorPlugin +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject +import okhttp3.Interceptor +import okhttp3.Interceptor.Chain +import okhttp3.Response + +@ContributesMultibinding( + scope = AppScope::class, + boundType = PixelInterceptorPlugin::class, +) +class AutofillPixelInterceptor @Inject constructor( + private val autofillStore: AutofillPrefsStore, +) : PixelInterceptorPlugin, Interceptor { + + private fun isInPixelsList(pixel: String): Boolean { + return pixels.firstOrNull { pixel.startsWith(it.pixelName) } != null + } + + override fun intercept(chain: Chain): Response { + val request = chain.request().newBuilder() + val pixel = chain.request().url.pathSegments.last().removeSuffixes() + + val url = if (isInPixelsList(pixel)) { + val defaultState = autofillStore.wasDefaultStateEnabled() + chain.request().url.newBuilder().addQueryParameter(AUTOFILL_DEFAULT_STATE, defaultState.asDefaultStateParam()).build() + } else { + chain.request().url + } + + return chain.proceed(request.url(url).build()) + } + + override fun getInterceptor(): Interceptor = this + + private fun Boolean.asDefaultStateParam(): String { + return if (this) "on" else "off" + } + + private fun String.removeSuffixes(): String { + return this + .removeSuffix("_android_phone") + .removeSuffix("_android_tablet") + } + + companion object { + + val pixels = listOf( + AUTOFILL_SAVE_LOGIN_PROMPT_SHOWN, + AUTOFILL_SAVE_LOGIN_PROMPT_SAVED, + AUTOFILL_SAVE_LOGIN_PROMPT_DISMISSED, + + AUTOFILL_SAVE_PASSWORD_PROMPT_SHOWN, + AUTOFILL_SAVE_PASSWORD_PROMPT_SAVED, + AUTOFILL_SAVE_PASSWORD_PROMPT_DISMISSED, + + AUTOFILL_SELECT_LOGIN_PROMPT_SHOWN, + AUTOFILL_SELECT_LOGIN_PROMPT_SELECTED, + AUTOFILL_SELECT_LOGIN_PROMPT_DISMISSED, + + AUTOFILL_SELECT_LOGIN_AUTOPROMPT_SHOWN, + AUTOFILL_SELECT_LOGIN_AUTOPROMPT_SELECTED, + AUTOFILL_SELECT_LOGIN_AUTOPROMPT_DISMISSED, + + AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SHOWN, + AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_KEEP_USING, + AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_DISABLE, + + AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED, + AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED, + + AUTOFILL_PASSWORD_GENERATION_PROMPT_SHOWN, + AUTOFILL_PASSWORD_GENERATION_ACCEPTED, + AUTOFILL_PASSWORD_GENERATION_PROMPT_DISMISSED, + + MENU_ACTION_AUTOFILL_PRESSED, + SETTINGS_AUTOFILL_MANAGEMENT_OPENED, + ) + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelNames.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelNames.kt index f60c4859b4fe..dc9a72d500e9 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelNames.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelNames.kt @@ -63,6 +63,9 @@ enum class AutofillPixelNames(override val pixelName: String) : Pixel.PixelName AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED("m_autofill_logins_settings_enabled"), AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED("m_autofill_logins_settings_disabled"), + MENU_ACTION_AUTOFILL_PRESSED("m_nav_autofill_menu_item_pressed"), + SETTINGS_AUTOFILL_MANAGEMENT_OPENED("m_autofill_settings_opened"), + EMAIL_USE_ALIAS("email_filled_random"), EMAIL_USE_ADDRESS("email_filled_main"), EMAIL_TOOLTIP_DISMISSED("email_tooltip_dismissed"), @@ -81,6 +84,10 @@ enum class AutofillPixelNames(override val pixelName: String) : Pixel.PixelName EMAIL_PROTECTION_IN_CONTEXT_MODAL_EXIT_EARLY_CONFIRM("m_email_incontext_modal_exit_early"), } +object AutofillPixelParameters { + const val AUTOFILL_DEFAULT_STATE = "default_state" +} + @ContributesMultibinding( scope = AppScope::class, boundType = PixelParamRemovalPlugin::class, diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/systemautofill/SystemAutofillServiceSuppressor.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/systemautofill/SystemAutofillServiceSuppressor.kt new file mode 100644 index 000000000000..dc3f183ef974 --- /dev/null +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/systemautofill/SystemAutofillServiceSuppressor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.impl.systemautofill + +import android.annotation.SuppressLint +import android.os.Build +import android.view.autofill.AutofillManager +import android.webkit.WebView +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface SystemAutofillServiceSuppressor { + fun suppressAutofill(webView: WebView?) +} + +@ContributesBinding(AppScope::class) +class RealSystemAutofillServiceSuppressor @Inject constructor( + private val appBuildConfig: AppBuildConfig, +) : SystemAutofillServiceSuppressor { + + @SuppressLint("NewApi") + override fun suppressAutofill(webView: WebView?) { + if (appBuildConfig.sdkInt >= Build.VERSION_CODES.O) { + webView?.context?.getSystemService(AutofillManager::class.java)?.cancel() + } + } +} diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt index d7d44cc1a618..cc61b0310fd3 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt @@ -97,6 +97,15 @@ class AutofillManagementActivity : DuckDuckGoActivity() { setContentView(binding.root) setupToolbar(binding.toolbar) observeViewModel() + sendLaunchPixel(savedInstanceState) + } + + private fun sendLaunchPixel(savedInstanceState: Bundle?) { + if (savedInstanceState == null) { + val launchedFromBrowser = intent.hasExtra(EXTRAS_SUGGESTIONS_FOR_URL) + val directLinkToCredentials = intent.hasExtra(EXTRAS_CREDENTIALS_TO_VIEW) + viewModel.sendLaunchPixel(launchedFromBrowser, directLinkToCredentials) + } } override fun onStart() { diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModel.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModel.kt index 5d9b73728e9a..60ede9c817d5 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModel.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModel.kt @@ -28,6 +28,8 @@ import com.duckduckgo.autofill.api.store.AutofillStore import com.duckduckgo.autofill.impl.deviceauth.DeviceAuthenticator import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.MENU_ACTION_AUTOFILL_PRESSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.SETTINGS_AUTOFILL_MANAGEMENT_OPENED import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.Command.ExitCredentialMode import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.Command.ExitDisabledMode import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.Command.ExitListMode @@ -522,6 +524,25 @@ class AutofillSettingsViewModel @Inject constructor( } } + /** + * Responsible for sending pixels which were previously managed in the app module. + * + * There are multiple ways to launch this screen, which should map to existing pixels where they exist. + */ + fun sendLaunchPixel(launchedFromBrowser: Boolean, directLinkToCredentials: Boolean) { + // no existing pixel for this scenario; don't want it to inflate other existing pixels + if (directLinkToCredentials) return + + // map scenario onto existing pixels + val pixelName = if (launchedFromBrowser) { + MENU_ACTION_AUTOFILL_PRESSED + } else { + SETTINGS_AUTOFILL_MANAGEMENT_OPENED + } + + pixel.fire(pixelName) + } + data class ViewState( val autofillEnabled: Boolean = true, val showAutofillEnabledToggle: Boolean = true, diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/AutofillSavingCredentialsDialogFragment.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/AutofillSavingCredentialsDialogFragment.kt index 1b9b1e731bf8..ec5b4ac28a22 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/AutofillSavingCredentialsDialogFragment.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/AutofillSavingCredentialsDialogFragment.kt @@ -140,7 +140,6 @@ class AutofillSavingCredentialsDialogFragment : BottomSheetDialogFragment(), Cre credentials: LoginCredentials, ) { (dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED - configureSiteDetails(binding, credentials) configureCloseButtons(binding) configureSaveButton(binding) } @@ -199,26 +198,6 @@ class AutofillSavingCredentialsDialogFragment : BottomSheetDialogFragment(), Cre (dialog as BottomSheetDialog).animateClosed() } - private fun configureSiteDetails( - binding: ContentAutofillSaveNewCredentialsBinding, - credentials: LoginCredentials, - ) { - val originalUrl = getOriginalUrl() - val url = originalUrl.extractDomain() ?: originalUrl - - binding.siteName.text = url - val placeholder = initialExtractor.extractInitial(credentials) - - lifecycleScope.launch { - faviconManager.loadToViewFromLocalWithPlaceholder( - tabId = getTabId(), - url = originalUrl, - view = binding.favicon, - placeholder = placeholder, - ) - } - } - private fun pixelNameDialogEvent(dialogEvent: DialogEvent): AutofillPixelNames? { val saveType = getCredentialsToSave().saveType() return when (dialogEvent) { diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounter.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounter.kt index 2b62572b4d03..c166e35d291a 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounter.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounter.kt @@ -136,6 +136,6 @@ class AutofillDisablingDeclineCounter @Inject constructor( } companion object { - private const val GLOBAL_DECLINE_COUNT_THRESHOLD = 3 + private const val GLOBAL_DECLINE_COUNT_THRESHOLD = 2 } } diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/selecting/AutofillSelectCredentialsDialogFragment.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/selecting/AutofillSelectCredentialsDialogFragment.kt index edc7d8e9ddf1..02eda26dab3a 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/selecting/AutofillSelectCredentialsDialogFragment.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/selecting/AutofillSelectCredentialsDialogFragment.kt @@ -23,7 +23,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.setFragmentResult -import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.app.browser.favicon.FaviconManager import com.duckduckgo.app.statistics.pixels.Pixel @@ -45,14 +44,12 @@ import com.duckduckgo.autofill.impl.ui.credential.selecting.AutofillSelectCreden import com.duckduckgo.autofill.impl.ui.credential.selecting.AutofillSelectCredentialsDialogFragment.DialogEvent.Selected import com.duckduckgo.autofill.impl.ui.credential.selecting.AutofillSelectCredentialsDialogFragment.DialogEvent.Shown import com.duckduckgo.autofill.impl.ui.credential.selecting.CredentialsPickerRecyclerAdapter.ListItem -import com.duckduckgo.common.utils.extractDomain import com.duckduckgo.di.scopes.FragmentScope import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.android.support.AndroidSupportInjection import javax.inject.Inject -import kotlinx.coroutines.launch import timber.log.Timber @InjectWith(FragmentScope::class) @@ -107,7 +104,6 @@ class AutofillSelectCredentialsDialogFragment : BottomSheetDialogFragment(), Cre private fun configureViews(binding: ContentAutofillSelectCredentialsTooltipBinding) { (dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED val originalUrl = getOriginalUrl() - configureSiteDetails(originalUrl, binding) configureRecyclerView(originalUrl, binding) configureCloseButton(binding) } @@ -116,19 +112,6 @@ class AutofillSelectCredentialsDialogFragment : BottomSheetDialogFragment(), Cre binding.closeButton.setOnClickListener { (dialog as BottomSheetDialog).animateClosed() } } - private fun configureSiteDetails( - originalUrl: String, - binding: ContentAutofillSelectCredentialsTooltipBinding, - ) { - val url = originalUrl.extractDomain() ?: originalUrl - - binding.siteName.text = url - - lifecycleScope.launch { - faviconManager.loadToViewFromLocalWithPlaceholder(url = url, view = binding.favicon) - } - } - private fun configureRecyclerView( originalUrl: String, binding: ContentAutofillSelectCredentialsTooltipBinding, diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/updating/AutofillUpdatingExistingCredentialsDialogFragment.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/updating/AutofillUpdatingExistingCredentialsDialogFragment.kt index fe217ee8c146..ee6fd6d155df 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/updating/AutofillUpdatingExistingCredentialsDialogFragment.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/updating/AutofillUpdatingExistingCredentialsDialogFragment.kt @@ -24,9 +24,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.setFragmentResult import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.InjectWith -import com.duckduckgo.app.browser.favicon.FaviconManager import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.autofill.api.CredentialUpdateExistingCredentialsDialog import com.duckduckgo.autofill.api.CredentialUpdateExistingCredentialsDialog.CredentialUpdateType @@ -43,14 +41,12 @@ import com.duckduckgo.autofill.impl.ui.credential.updating.AutofillUpdatingExist import com.duckduckgo.autofill.impl.ui.credential.updating.AutofillUpdatingExistingCredentialsDialogFragment.DialogEvent.Shown import com.duckduckgo.autofill.impl.ui.credential.updating.AutofillUpdatingExistingCredentialsDialogFragment.DialogEvent.Updated import com.duckduckgo.common.utils.FragmentViewModelFactory -import com.duckduckgo.common.utils.extractDomain import com.duckduckgo.di.scopes.FragmentScope import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.android.support.AndroidSupportInjection import javax.inject.Inject -import kotlinx.coroutines.launch import timber.log.Timber @InjectWith(FragmentScope::class) @@ -58,9 +54,6 @@ class AutofillUpdatingExistingCredentialsDialogFragment : BottomSheetDialogFragm override fun getTheme(): Int = R.style.AutofillBottomSheetDialogTheme - @Inject - lateinit var faviconManager: FaviconManager - @Inject lateinit var viewModelFactory: FragmentViewModelFactory @@ -116,7 +109,6 @@ class AutofillUpdatingExistingCredentialsDialogFragment : BottomSheetDialogFragm Timber.v("Update type is $updateType") configureDialogTitle(binding, updateType) - configureSiteDetails(binding, originalUrl) configureCloseButtons(binding) configureUpdatedFieldPreview(binding, credentials, updateType) configureUpdateButton(binding, originalUrl, credentials, updateType) @@ -193,19 +185,6 @@ class AutofillUpdatingExistingCredentialsDialogFragment : BottomSheetDialogFragm pixelNameDialogEvent(Dismissed)?.let { pixel.fire(it) } } - private fun configureSiteDetails( - binding: ContentAutofillUpdateExistingCredentialsBinding, - originalUrl: String, - ) { - val url = originalUrl.extractDomain() ?: originalUrl - - binding.siteName.text = url - - lifecycleScope.launch { - faviconManager.loadToViewFromLocalWithPlaceholder(url = url, view = binding.favicon) - } - } - private fun pixelNameDialogEvent(dialogEvent: DialogEvent): AutofillPixelNames? { return when (dialogEvent) { is Shown -> AUTOFILL_UPDATE_LOGIN_PROMPT_SHOWN diff --git a/autofill/autofill-impl/src/main/res/layout/autofill_management_credential_list_empty_state.xml b/autofill/autofill-impl/src/main/res/layout/autofill_management_credential_list_empty_state.xml index ddee421ef890..c956e6ec36ba 100644 --- a/autofill/autofill-impl/src/main/res/layout/autofill_management_credential_list_empty_state.xml +++ b/autofill/autofill-impl/src/main/res/layout/autofill_management_credential_list_empty_state.xml @@ -52,22 +52,7 @@ android:text="@string/credentialManagementNoLoginsSavedTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toTopOf="@id/emptyPlaceholderSubtitle" - app:layout_constraintTop_toBottomOf="@+id/autofillKeyIcon" /> - - + app:layout_constraintTop_toBottomOf="@+id/autofillKeyIcon" /> diff --git a/autofill/autofill-impl/src/main/res/layout/content_autofill_generate_password_dialog.xml b/autofill/autofill-impl/src/main/res/layout/content_autofill_generate_password_dialog.xml index 0c82ece7dd23..a81a432a8798 100644 --- a/autofill/autofill-impl/src/main/res/layout/content_autofill_generate_password_dialog.xml +++ b/autofill/autofill-impl/src/main/res/layout/content_autofill_generate_password_dialog.xml @@ -28,86 +28,116 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + + + + + app:layout_constraintWidth_max="@dimen/autofillBottomSheetContentMaxWidth" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/guidelineStart" + app:layout_constraintEnd_toEndOf="@id/guidelineEnd"> - + - + - + - + - + + + + + + + \ No newline at end of file diff --git a/autofill/autofill-impl/src/main/res/layout/content_autofill_save_new_credentials.xml b/autofill/autofill-impl/src/main/res/layout/content_autofill_save_new_credentials.xml index 1be418061cce..802b9ae08b71 100644 --- a/autofill/autofill-impl/src/main/res/layout/content_autofill_save_new_credentials.xml +++ b/autofill/autofill-impl/src/main/res/layout/content_autofill_save_new_credentials.xml @@ -14,117 +14,98 @@ ~ limitations under the License. --> - - + + + + style="@style/AutofillDialogContentGuidelineStart" /> - - + style="@style/AutofillDialogContentGuidelineEnd" /> - - - + app:layout_constraintEnd_toEndOf="@id/guidelineEnd"> + + - - - - - - - - - - - + android:id="@+id/dialogTitle" + android:breakStrategy="balanced" + android:text="@string/saveLoginDialogTitle" + android:layout_marginTop="@dimen/keyline_5" + android:gravity="center_horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/appIcon" + app:typography="h2" /> + + + + + + + diff --git a/autofill/autofill-impl/src/main/res/layout/content_autofill_select_credentials_tooltip.xml b/autofill/autofill-impl/src/main/res/layout/content_autofill_select_credentials_tooltip.xml index d6fb3d1c0a83..7a8abe655f8a 100644 --- a/autofill/autofill-impl/src/main/res/layout/content_autofill_select_credentials_tooltip.xml +++ b/autofill/autofill-impl/src/main/res/layout/content_autofill_select_credentials_tooltip.xml @@ -14,8 +14,7 @@ ~ limitations under the License. --> - @@ -26,59 +25,60 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + + + + + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_max="@dimen/autofillBottomSheetContentMaxWidth" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/guidelineStart" + app:layout_constraintEnd_toEndOf="@id/guidelineEnd"> - + app:srcCompat="@drawable/ic_dax_icon" /> - - - - + android:id="@+id/useLoginTitle" + android:text="@string/useSavedLoginDialogTitle" + android:layout_marginTop="@dimen/keyline_5" + android:gravity="center_horizontal" + app:layout_constraintBottom_toTopOf="@id/availableCredentialsRecycler" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/appIcon" + app:typography="h2" /> - + + \ No newline at end of file diff --git a/autofill/autofill-impl/src/main/res/layout/content_autofill_update_existing_credentials.xml b/autofill/autofill-impl/src/main/res/layout/content_autofill_update_existing_credentials.xml index e6f86dbaed6e..8bb52ee8a828 100644 --- a/autofill/autofill-impl/src/main/res/layout/content_autofill_update_existing_credentials.xml +++ b/autofill/autofill-impl/src/main/res/layout/content_autofill_update_existing_credentials.xml @@ -28,82 +28,85 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + + + + + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_max="@dimen/autofillBottomSheetContentMaxWidth" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/guidelineStart" + app:layout_constraintEnd_toEndOf="@id/guidelineEnd"> - + - - + android:id="@+id/dialogTitle" + android:layout_marginTop="@dimen/keyline_5" + android:layout_marginStart="@dimen/keyline_4" + android:layout_marginEnd="@dimen/keyline_4" + tools:text="Update password for\nUSERNAME?" + android:gravity="center_horizontal" + app:layout_constraintBottom_toTopOf="@id/dialogSubtitle" + app:layout_constraintEnd_toEndOf="@id/updateCredentialsButton" + app:layout_constraintStart_toStartOf="@id/updateCredentialsButton" + app:layout_constraintTop_toBottomOf="@id/appIcon" + app:typography="h2" /> - - - + - + - + + \ No newline at end of file diff --git a/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_choose_email.xml b/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_choose_email.xml index 73b5829f6234..d8527c7dd340 100644 --- a/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_choose_email.xml +++ b/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_choose_email.xml @@ -30,52 +30,65 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + + + + + android:layout_marginTop="40dp" + app:layout_constraintWidth_max="@dimen/autofillBottomSheetContentMaxWidth" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/guidelineStart" + app:layout_constraintEnd_toEndOf="@id/guidelineEnd"> - + - + + + + diff --git a/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_in_context_sign_up.xml b/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_in_context_sign_up.xml index fafe2dce753f..54e5f7ceffd9 100644 --- a/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_in_context_sign_up.xml +++ b/autofill/autofill-impl/src/main/res/layout/dialog_email_protection_in_context_sign_up.xml @@ -16,110 +16,110 @@ - - - - - + + + + + app:layout_constraintEnd_toEndOf="@id/guidelineEnd"> - + + + + + + + - - + android:id="@+id/dialogTitle" + android:breakStrategy="balanced" + android:text="@string/autofillEmailProtectionInContextSignUpDialogTitle" + android:layout_marginTop="@dimen/keyline_5" + android:gravity="center_horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/siteDetailsContainer" + app:typography="h2" /> - - - - - - - + + + + + + diff --git a/autofill/autofill-impl/src/main/res/layout/fragment_autofill_management_list_mode.xml b/autofill/autofill-impl/src/main/res/layout/fragment_autofill_management_list_mode.xml index 07b652814e73..9eb1e682dc67 100644 --- a/autofill/autofill-impl/src/main/res/layout/fragment_autofill_management_list_mode.xml +++ b/autofill/autofill-impl/src/main/res/layout/fragment_autofill_management_list_mode.xml @@ -26,12 +26,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - За защита на данните за вход е необходимо да зададете модел за заключване, ПИН код или биометрични данни. Защитете устройството, за да можете да запазвате данни за вход - Запазване на данните за вход + Запазване на паролата Запазване на паролата - Не сега - Данните за вход са сигурно съхранени само на Вашето устройство и могат да се управляват от менюто Данни за вход в Настройки. - Искате ли DuckDuckGo да запази данните за вход? + Не записвай + Паролите се съхраняват на сигурно място в менюто Данни за вход на Вашето устройство. + Искате ли DuckDuckGo да запази Вашата парола? Повече опции - Да се използват ли запазените данни за вход? + Използване на запазена парола? Парола за %1$s Парола за сайта От този уебсайт От %1$s Актуализиране на паролата - Не сега + Не записвай Актуализиране на потребителското име? Актуализиране на потребителското име Актуализиране на паролата за \n%1$s? - DuckDuckGo ще актуализира запазените данни за вход във Вашето устройство. + DuckDuckGo ще актуализира запазената парола във Вашето устройство. Затваряне на диалоговия прозорец за автоматично попълване @@ -60,12 +60,12 @@ Последна актуализация на данните за вход %1$s Запазване и автоматично попълване на данните за вход + Данните за вход се съхраняват сигурно на Вашето устройство Редактиране Изтриване Предложения Все още няма запазени данни за вход - Данните за вход се съхраняват сигурно на Вашето устройство. Добавяне на данни за вход Търсене на данни за вход @@ -79,9 +79,9 @@ Потребителското име е копирано Паролата е копирана - Използване на парола, генерирана от DuckDuckGo? - Паролата ще бъде запазена в „Данни за вход“. - Използване на генерирана парола + Използване на силна парола от DuckDuckGo? + Паролите се съхраняват на сигурно място в менюто Данни за вход на Вашето устройство. + Използване на силна парола Създаване на собствена Устройството не се поддържа diff --git a/autofill/autofill-impl/src/main/res/values-cs/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-cs/strings-autofill-impl.xml index 7917c8e6dfda..7503d970598e 100644 --- a/autofill/autofill-impl/src/main/res/values-cs/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-cs/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Na ochranu přihlašovacích údajů se vyžaduje gesto zámku obrazovky, PIN nebo biometrické ověření. Zabezpeč si zařízení, ať si můžeš ukládat přihlašovací údaje - Uložit přihlašovací údaje + Uložit heslo Uložit heslo - Teď ne - Přihlašovací údaje se bezpečně ukládají do tvého zařízení a dají se spravovat v nabídce Přihlášení v Nastavení. - Má DuckDuckGo uložit přihlašovací údaje? + Neukládat + Hesla jsou bezpečně uložená v zařízení v nabídce Přihlášení. + Má DuckDuckGo uložit heslo? Další možnosti - Použít uložené přihlášení? + Použít uložené heslo? Heslo pro %1$s Heslo pro web Z této webové stránky Ze stránky %1$ss Aktualizuj si heslo - Teď ne + Neukládat Aktualizovat uživatelské jméno? Aktualizovat uživatelské jméno Aktualizovat heslo pro \n%1$s? - DuckDuckGo zaktualizuje toto uložené přihlášení ve tvém zařízení. + DuckDuckGo aktualizuje tohle uložené heslo ve tvém zařízení. Zavřít dialogové okno Automatické vyplňování @@ -60,12 +60,12 @@ Přihlášení naposledy aktualizováno %1$s Uložit a automaticky vyplnit přihlášení + Přihlašovací údaje se bezpečně ukládají do tvého zařízení Upravit Smazat Navrhované Zatím nemáš uložené žádné přihlašovací údaje - Přihlašovací údaje se bezpečně ukládají do tvého zařízení. Přidat přihlášení Hledání přihlašovacích údajů @@ -79,9 +79,9 @@ Uživatelské jméno zkopírováno Heslo zkopírováno - Použít heslo vygenerované DuckDuckGo? - Heslo se uloží do části Přihlášení. - Použít vygenerované heslo + Použít silné heslo od DuckDuckGo? + Hesla jsou bezpečně uložená v zařízení v nabídce Přihlášení. + Použít silné heslo Vytvořit vlastní Zařízení není podporované diff --git a/autofill/autofill-impl/src/main/res/values-da/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-da/strings-autofill-impl.xml index 19aed03f1905..dd6489c07006 100644 --- a/autofill/autofill-impl/src/main/res/values-da/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-da/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Der kræves et låsemønster, en pinkode eller biometri for at beskytte dine logins. Beskyt din enhed for at gemme logins - Gem login + Gem adgangskode Gem adgangskode - Ikke nu - Logins gemmes sikkert på din enhed og kan administreres fra menuen Logins i Indstillinger. - Skal DuckDuckGo gemme dit login? + Gem ikke + Adgangskoder gemmes sikkert på din enhed i menuen Logins. + Skal DuckDuckGo gemme din adgangskode? Flere muligheder - Vil du bruge et gemt login? + Brug en gemt adgangskode? Adgangskode til %1$s Adgangskode til websted Fra dette websted Fra %1$s Opdater adgangskode - Ikke nu + Gem ikke Opdater brugernavn? Opdater brugernavn Opdater adgangskode for \n%1$s? - DuckDuckGo opdaterer dette gemte login på din enhed. + DuckDuckGo opdaterer denne gemte adgangskode på din enhed. Luk dialogboksen for automatisk udfyldning @@ -60,12 +60,12 @@ Login sidst opdateret %1$s Gem og udfyld automatisk logins + Login gemmes sikkert på din enhed Rediger Slet Foreslået Ingen logins gemt endnu - Login gemmes sikkert på din enhed. Tilføj login Søg logins @@ -79,9 +79,9 @@ Brugernavn kopieret Adgangskode kopieret - Brug genereret adgangskode fra DuckDuckGo? - Adgangskoden vil blive gemt i logins. - Brug genereret adgangskode + Brug en stærk adgangskode fra DuckDuckGo? + Adgangskoder gemmes sikkert på din enhed i menuen Logins. + Brug en stærk adgangskode Opret min egen Enheden understøttes ikke diff --git a/autofill/autofill-impl/src/main/res/values-de/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-de/strings-autofill-impl.xml index d93a5fc94121..9c2f929a9665 100644 --- a/autofill/autofill-impl/src/main/res/values-de/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-de/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Es sind ein Entsperrmuster, eine PIN oder biometrische Daten erforderlich, um deine Anmeldedaten zu schützen. Sichere dein Gerät, um Anmeldungen zu speichern - Anmeldedaten speichern + Passwort speichern Passwort speichern - Jetzt nicht - Die Anmeldedaten werden auf diesem Gerät sicher gespeichert und können über das Anmeldedaten-Menü in den Einstellungen verwaltet werden. - Möchtest du, dass DuckDuckGo deine Anmeldedaten speichert? + Nicht speichern + Passwörter werden sicher auf deinem Gerät im Anmeldedaten-Menü gespeichert. + Möchtest du, dass DuckDuckGo dein Passwort speichert? Weitere Optionen - Gespeicherte Anmeldedaten verwenden? + Gespeichertes Passwort verwenden? Passwort für %1$s Passwort für die Website Von dieser Website Von %1$s Passwort aktualisieren - Jetzt nicht + Nicht speichern Benutzernamen aktualisieren? Benutzernamen aktualisieren Passwort für \n%1$s aktualisieren? - DuckDuckGo aktualisiert diese gespeicherten Anmeldedaten auf deinem Gerät. + DuckDuckGo aktualisiert dieses gespeicherte Passwort auf deinem Gerät. Autovervollständigen-Dialog schließen @@ -60,12 +60,12 @@ Anmeldedaten zuletzt aktualisiert: %1$s Anmeldedaten speichern und automatisch ausfüllen + Die Anmeldedaten werden sicher auf deinem Gerät gespeichert Bearbeiten Löschen Vorgeschlagen Noch keine Anmeldedaten gespeichert - Die Anmeldedaten werden sicher auf deinem Gerät gespeichert. Anmeldedaten hinzufügen Anmeldedaten suchen @@ -79,9 +79,9 @@ Benutzername kopiert Passwort kopiert - Generiertes Passwort von DuckDuckGo verwenden? - Das Passwort wird in den Anmeldedaten gespeichert. - Generiertes Passwort verwenden + Ein sicheres Passwort von DuckDuckGo verwenden? + Passwörter werden sicher auf deinem Gerät im Anmeldedaten-Menü gespeichert. + Sicheres Passwort verwenden Eigenes erstellen Gerät nicht unterstützt diff --git a/autofill/autofill-impl/src/main/res/values-el/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-el/strings-autofill-impl.xml index 92652b943f79..1903f460c57a 100644 --- a/autofill/autofill-impl/src/main/res/values-el/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-el/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Για προστασία των «Συνδέσεών» σας απαιτείται μοτίβο κλειδώματος συσκευής, PIN ή βιομετρικά στοιχεία. Ασφαλίστε τη συσκευή σας για να αποθηκεύετε τις «Συνδέσεις» - Αποθήκευση σύνδεσης + Αποθήκευση κωδικού πρόσβασης Αποθήκευση κωδικού πρόσβασης - Όχι τώρα - Οι συνδέσεις αποθηκεύονται με ασφάλεια στη συσκευή σας και η διαχείρισή τους γίνεται από το μενού Συνδέσεις, στις Ρυθμίσεις. - Θέλετε να αποθηκεύσει το DuckDuckGo τη σύνδεσή σας; + Να μην αποθηκευτεί + Οι κωδικοί πρόσβασης αποθηκεύονται με ασφάλεια στη συσκευή σας στο μενού Συνδέσεις. + Θέλετε το DuckDuckGo να αποθηκεύσει τον κωδικό πρόσβασής σας; Περισσότερες επιλογές - Χρήση αποθηκευμένων στοιχείων σύνδεσης; + Χρήση αποθηκευμένου κωδικού πρόσβασης; Κωδικός πρόσβασης για %1$s Κωδικός πρόσβασης για τον ιστότοπο Από αυτόν τον ιστότοπο Από %1$s Ενημέρωση κωδικού πρόσβασης - Όχι τώρα + Να μην αποθηκευτεί Ενημέρωση ονόματος χρήστη; Ενημέρωση ονόματος χρήστη Ενημέρωση κωδικού πρόσβασης για \n%1$s; - Το DuckDuckGo θα ενημερώσει αυτήν την αποθηκευμένη σύνδεση στη συσκευή σας. + Το DuckDuckGo θα ενημερώσει αυτόν τον αποθηκευμένο κωδικό πρόσβασης στη συσκευή σας. Κλείσιμο διαλόγου αυτόματης συμπλήρωσης @@ -60,12 +60,12 @@ Τελευταία ενημέρωση σύνδεσης %1$s Αποθήκευση και αυτόματη συμπλήρωση στοιχείων σύνδεσης + Τα στοιχεία σύνδεσης αποθηκεύονται με ασφάλεια στη συσκευή σας Επεξεργασία Διαγραφή Προτεινόμενο Δεν έχουν αποθηκευτεί ακόμα στοιχεία σύνδεσης - Τα στοιχεία σύνδεσης αποθηκεύονται με ασφάλεια στη συσκευή σας. Προσθήκη στοιχείων σύνδεσης Αναζήτηση στοιχείων σύνδεσης @@ -79,9 +79,9 @@ Το όνομα χρήστη αντιγράφηκε Ο κωδικός πρόσβασης αντιγράφηκε - Χρήση κωδικού πρόσβασης που δημιουργήθηκε από το DuckDuckGo; - Ο κωδικός πρόσβασης θα αποθηκευτεί στις Συνδέσεις. - Χρήση του κωδικού πρόσβασης που δημιουργήθηκε + Χρησιμοποιείτε ισχυρό κωδικό πρόσβασης από το DuckDuckGo; + Οι κωδικοί πρόσβασης αποθηκεύονται με ασφάλεια στη συσκευή σας στο μενού Συνδέσεις. + Χρησιμοποιήστε ισχυρό κωδικό πρόσβασης Δημιουργία δικού μου Η συσκευή δεν υποστηρίζεται diff --git a/autofill/autofill-impl/src/main/res/values-es/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-es/strings-autofill-impl.xml index e3195b27dd0b..e166a8aabcd4 100644 --- a/autofill/autofill-impl/src/main/res/values-es/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-es/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Se requiere un patrón de bloqueo del dispositivo, un PIN o datos biométricos para proteger tus inicios de sesión. Protege tu dispositivo para guardar los inicios de sesión - Guardar inicio de sesión + Guardar contraseña Guardar contraseña - Ahora no - Los inicios de sesión se almacenan de forma segura en tu dispositivo y se pueden administrar desde el menú de Inicios de Sesión en Ajustes. - ¿Quieres que DuckDuckGo guarde tu inicio de sesión? + No guardar + Las contraseñas se almacenan de forma segura en tu dispositivo en el menú Inicios de sesión. + ¿Quieres que DuckDuckGo guarde tu contraseña? Más opciones - ¿Usar un inicio de sesión guardado? + ¿Usar una contraseña guardada? Contraseña para %1$s Contraseña para el sitio Desde este sitio web Desde %1$s Actualizar contraseña - Ahora no + No guardar ¿Actualizar nombre de usuario? Actualizar nombre de usuario ¿Actualizar contraseña de \n%1$s? - DuckDuckGo actualizará este inicio de sesión almacenado en tu dispositivo. + DuckDuckGo actualizará esta contraseña almacenada en tu dispositivo. Cerrar cuadro de diálogo de Autocompletar @@ -60,12 +60,12 @@ Última actualización de inicio de sesión %1$s Guardar y autocompletar inicios de sesión + Los inicios de sesión se almacenan de forma segura en tu dispositivo Editar Eliminar Sugerencias Aún no hay inicios de sesión guardados - Los inicios de sesión se almacenan de forma segura en tu dispositivo. Añadir inicio de sesión Buscar inicios de sesión @@ -79,9 +79,9 @@ Nombre de usuario copiado Contraseña copiada - ¿Usar contraseña generada por DuckDuckGo? - La contraseña se guardará en Inicios de sesión. - Usar contraseña generada + ¿Usar una contraseña segura de DuckDuckGo? + Las contraseñas se almacenan de forma segura en tu dispositivo en el menú Inicios de sesión. + Usar contraseña segura Crear la mía propia Dispositivo no compatible diff --git a/autofill/autofill-impl/src/main/res/values-et/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-et/strings-autofill-impl.xml index f503806f2e89..906ec9ebd29a 100644 --- a/autofill/autofill-impl/src/main/res/values-et/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-et/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Sisselogimise kaitsmiseks on vaja seadme lukustusmustrit, PIN-koodi või biomeetriat. Sisselogimise salvestamiseks kaitske oma seadet - Salvesta sisselogimisandmed + Salvesta parool Salvesta parool - Mitte praegu - Sisselogimisandmed salvestatakse turvaliselt selles seadmes ja neid saab hallata sätete menüüst Sisselogimisandmed. - Kas soovid, et DuckDuckGo salvestaks sinu sisselogimisandmed? + Ära salvesta + Paroole hoitakse turvaliselt sinu seadmes sisselogimisandmete menüüs. + Kas soovid, et DuckDuckGo salvestaks sinu parooli? Veel valikuid - Kas kasutada salvestatud sisselogimisandmeid? + Kas kasutada salvestatud parooli? Saidi %1$s parool Saidi parool Sellelt veebisaidilt Saidilt %1$s Värskenda parooli - Mitte praegu + Ära salvesta Kas värskendada kasutajanime? Värskenda kasutajanime Kas värskendada kasutajanime \n%1$s parooli? - DuckDuckGo värskendab need salvestatud sisselogimisandmed sinu seadmes. + DuckDuckGo värskendab selle salvestatud parooli sinu seadmes. Sulge automaatse täitmise dialoog @@ -60,12 +60,12 @@ Sisselogimist värskendati viimati %1$s Salvesta ja täida sisselogimisandmed automaatselt + Sisselogimisandmed salvestatakse turvaliselt sinu seadmesse Redigeeri Kustuta Soovitatud Sisselogimisandmeid pole veel salvestatud - Sisselogimisandmed salvestatakse turvaliselt sinu seadmesse. Lisa sisselogimisandmed Otsi sisselogimisandmeid @@ -79,9 +79,9 @@ Kasutajanimi on kopeeritud Parool on kopeeritud - Kas kasutada DuckDuckGo loodud parooli? - Parool salvestatakse sisselogimisandmetesse. - Kasuta loodud parooli + Kas kasutada DuckDuckGo tugevat parooli? + Paroole hoitakse turvaliselt sinu seadmes sisselogimisandmete menüüs. + Kasuta tugevat parooli Loon selle ise Seadet ei toetata diff --git a/autofill/autofill-impl/src/main/res/values-fi/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-fi/strings-autofill-impl.xml index f7f3a87944e5..2fdaf299d0d7 100644 --- a/autofill/autofill-impl/src/main/res/values-fi/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-fi/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Kirjautumistietojen suojaamiseen tarvitaan laitteen lukituskuvio, PIN-koodi tai biometriset tiedot. Suojaa laitteesi kirjautumistietojen tallentamiseksi - Tallenna kirjautumistiedot + Tallenna salasana Tallenna salasana - Ei nyt - Kirjautumistiedot tallennetaan turvallisesti laitteellesi. Niitä voi hallita asetusten kirjautumistiedot-valikosta. - Haluatko, että DuckDuckGo tallentaa kirjautumistietosi? + Älä tallenna + Salasanat tallennetaan turvallisesti laitteellesi kirjautumistiedot-valikkoon. + Haluatko, että DuckDuckGo tallentaa salasanasi? Lisää vaihtoehtoja - Käytä tallennettuja kirjautumistietoja? + Käytetäänkö tallennettua salasanaa? Sivuston %1$s salasana Sivuston salasana Tältä sivustolta Sivustolta %1$s Päivitä salasana - Ei nyt + Älä tallenna Päivitetäänkö käyttäjätunnus? Päivitä käyttäjätunnus Päivitä käyttäjän \n%1$s salasana? - DuckDuckGo päivittää tämän laitteellesi tallennetun kirjautumistiedon. + DuckDuckGo päivittää tämän tallennetun salasanan laitteellesi. Sulje automaattisen täytön valintaikkuna @@ -60,12 +60,12 @@ Kirjautumistiedot päivitetty viimeksi %1$s Tallenna ja täytä kirjautumistiedot automaattisesti + Kirjautumiset tallennetaan laitteellesi turvallisesti Muokkaa Poista Ehdotettu Kirjautumistietoja ei ole vielä tallennettu - Kirjautumiset tallennetaan laitteellesi turvallisesti. Lisää kirjautumistiedot Hae kirjautumistietoja @@ -79,9 +79,9 @@ Käyttäjätunnus kopioitu Salasana kopioitu - Haluatko käyttää DuckDuckGon luomaa salasanaa? - Salasana tallennetaan Kirjautumiset-osioon. - Käytä luotua salasanaa + Käytetäänkö DuckDuckGon vahvaa salasanaa? + Salasanat tallennetaan turvallisesti laitteellesi kirjautumistiedot-valikkoon. + Käytä vahvaa salasanaa Luo oma Laitetta ei tueta diff --git a/autofill/autofill-impl/src/main/res/values-fr/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-fr/strings-autofill-impl.xml index 50789e2de8f7..9d08d8a50870 100644 --- a/autofill/autofill-impl/src/main/res/values-fr/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-fr/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Un schéma de verrouillage de l\'appareil, un code PIN ou des données biométriques sont nécessaires pour protéger vos identifiants. Sécurisez votre appareil pour enregistrer des identifiants - Enregistrer l\'identifiant + Enregistrer le mot de passe Enregistrer le mot de passe - Pas maintenant - Les identifiants sont stockés en toute sécurité sur votre appareil et peuvent être gérés à partir du menu Identifiants dans Paramètres. - Voulez-vous que DuckDuckGo enregistre votre identifiant ? + Ne pas enregistrer + Les mots de passe sont stockés en toute sécurité sur votre appareil dans le menu Identifiants. + Voulez-vous que DuckDuckGo enregistre votre mot de passe ? Plus d\'options - Utiliser un identifiant enregistré ? + Utiliser un mot de passe enregistré ? Mot de passe pour %1$s Mot de passe du site À partir de ce site Web À partir de %1$s Modifier le mot de passe - Pas maintenant + Ne pas enregistrer Modifier le nom d\'utilisateur ? Modifier le nom d\'utilisateur Mettre à jour le mot de passe pour \n%1$s ? - DuckDuckGo mettra à jour cet identifiant enregistré sur votre appareil. + DuckDuckGo mettra à jour ce mot de passe enregistré sur votre appareil. Fermer la boîte de dialogue de saisie automatique @@ -60,12 +60,12 @@ Dernière mise à jour de l\'identifiant %1$s Enregistrer et saisir automatiquement les identifiants + Les identifiants de connexion sont stockés en toute sécurité sur votre appareil Modifier Supprimer Suggéré Aucun identifiant enregistré pour l\'instant - Les identifiants de connexion sont stockés en toute sécurité sur votre appareil. Ajouter un identifiant Recherche d\'identifiants @@ -79,9 +79,9 @@ Nom d\'utilisateur copié Mot de passe copié - Utiliser le mot de passe généré par DuckDuckGo ? - Le mot de passe sera enregistré dans les identifiants. - Utiliser le mot de passe généré + Utiliser un mot de passe fort généré par DuckDuckGo ? + Les mots de passe sont stockés en toute sécurité sur votre appareil dans le menu Identifiants. + Utiliser un mot de passe fort Créer le mien Appareil non pris en charge diff --git a/autofill/autofill-impl/src/main/res/values-hr/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-hr/strings-autofill-impl.xml index 9a5b6fcf845f..3ae19746ccc5 100644 --- a/autofill/autofill-impl/src/main/res/values-hr/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-hr/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Uzorak za zaključavanje uređaja, PIN ili biometrija potrebni su za zaštitu vaših prijava. Osigurajte svoj uređaj da biste spremili prijave - Spremi prijavu + Spremi lozinku Spremi lozinku - Ne sada - Prijave se sigurno spremaju na tvom uređaju i njima se može upravljati putem izbornika Prijave u Postavkama. - Želiš li da DuckDuckGo spremi tvoju prijavu? + Nemoj spremiti + Lozinke su sigurno pohranjene na tvom uređaju u izborniku Prijava. + Želiš li da DuckDuckGo spremi tvoju lozinku? Dodatne mogućnosti - Koristiti spremljene podatke za prijavu? + Koristiti spremljenu lozinku? Lozinka za %1$s Lozinka za web lokaciju S ove web lokacije S %1$s Ažuriraj lozinku - Ne sada + Nemoj spremiti Želiš li ažurirati korisničko ime? Ažuriraj korisničko ime Želiš li ažurirati lozinku za korisnika \n%1$s? - DuckDuckGo će ažurirati ovu spremljenu prijavu na tvom uređaju. + DuckDuckGo će ažurirati ovu pohranjenu lozinku na tvom uređaju. Zatvori dijaloški okvir za automatsko popunjavanje @@ -60,12 +60,12 @@ Prijava zadnji put ažurirana %1$s Spremi i automatski popuni prijave + Prijave su sigurno pohranjene na tvom uređaju Uredi Izbriši Predloženo Još nema spremljenih prijava - Prijave su sigurno pohranjene na tvom uređaju. Dodaj prijavu Pretraživanje prijava @@ -79,9 +79,9 @@ Korisničko ime je kopirano Lozinka je kopirana - Koristiti generiranu lozinku iz DuckDuckGoa? - Lozinka će biti spremljena u Prijavama. - Koristi generiranu lozinku + Koristiti jaku lozinku od DuckDuckGoa? + Lozinke su sigurno pohranjene na tvom uređaju u izborniku Prijava. + Koristi jaku lozinku Izradi vlastitu Uređaj nije podržan diff --git a/autofill/autofill-impl/src/main/res/values-hu/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-hu/strings-autofill-impl.xml index 8f7dc8c1096f..f41407ba1167 100644 --- a/autofill/autofill-impl/src/main/res/values-hu/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-hu/strings-autofill-impl.xml @@ -23,27 +23,27 @@ A bejelentkezések védelméhez az eszköz zárolási mintája, PIN-kód vagy biometrikus adatok szükségesek. Tedd biztonságossá a készülékedet a bejelentkezések mentéséhez - Bejelentkezés mentése + Jelszó mentése Jelszó mentése - Most nem - A bejelentkezési adatokat az eszközöd biztonságosan tárolja, ezen adatok pedig a Beállítások > Bejelentkezések menüpontban kezelhetők. - A DuckDuckGo mentse a bejelentkezést? + Mentés mellőzése + A rendszer biztonságosan tárolja az eszköz Bejelentkezés menüjében elérhető jelszavakat. + A DuckDuckGo mentse a jelszót? További lehetőségek - Mentett bejelentkezés használata? + Mentett jelszót használsz? %1$s jelszava Webhely jelszava Erről a webhelyről Innen: %1$s Jelszó frissítése - Most nem + Ne mentse Frissíted a felhasználónevet? Felhasználónév frissítése Frissíted \n%1$s jelszavát? - A DuckDuckGo frissíti az eszközödön ezt a tárolt bejelentkezést. + A DuckDuckGo frissíti az eszközödön ezt a tárolt jelszót. Automatikus kitöltési párbeszédablak bezárása @@ -60,12 +60,12 @@ Bejelentkezés utolsó frissítése: %1$s Bejelentkezések mentése és automatikus kitöltése + A bejelentkezési adatokat az eszközöd biztonságosan tárolja Szerkesztés Törlés Javasolt Még nincsenek mentett bejelentkezési adatok - A bejelentkezési adatokat az eszközöd biztonságosan tárolja. Bejelentkezési adatok hozzáadása Bejelentkezési adatok keresése @@ -79,9 +79,9 @@ Felhasználónév másolva Jelszó másolva - DuckDuckGo által generált jelszó használata? - A jelszó mentve lesz a Bejelentkezésekben. - Generált jelszó használata + A DuckDuckGo által kínált erős jelszót kívánsz használni? + A rendszer biztonságosan tárolja az eszköz Bejelentkezés menüjében elérhető jelszavakat. + Erős jelszó használata Saját létrehozása Az eszköz nem támogatott diff --git a/autofill/autofill-impl/src/main/res/values-it/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-it/strings-autofill-impl.xml index 09fedeeda9cd..bbeb38815913 100644 --- a/autofill/autofill-impl/src/main/res/values-it/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-it/strings-autofill-impl.xml @@ -25,8 +25,8 @@ Salva dati di accesso Salva password - Non adesso - I dati di accesso sono archiviati in modo sicuro sul tuo dispositivo e possono essere gestiti dal menu Accessi, in Impostazioni. + Non salvare + Gli accessi sono archiviati in modo sicuro sul tuo dispositivo nel menu Accessi. Vuoi che DuckDuckGo salvi i dati di accesso? Altre opzioni @@ -37,7 +37,7 @@ Da %1$s Aggiorna password - Non adesso + Non salvare Aggiornare nome utente? Aggiorna nome utente @@ -60,12 +60,12 @@ Ultimo aggiornamento dei dati di accesso %1$s Salva e compila automaticamente i dati di accesso + I dati di accesso sono archiviati in modo sicuro sul tuo dispositivo Modifica Cancella Suggerimenti Non ci sono ancora dati di accesso salvati - I dati di accesso sono archiviati in modo sicuro sul tuo dispositivo. Aggiungi dati di accesso Cerca dati di accesso @@ -79,9 +79,9 @@ Nome utente copiato Password copiata - Usare la password generata da DuckDuckGo? - La password verrà salvata in Accessi. - Usa password generata + Usare una password complessa da DuckDuckGo? + Le password sono archiviate in modo sicuro sul tuo dispositivo nel menu Accessi. + Usa una password complessa Crea la mia Dispositivo non supportato diff --git a/autofill/autofill-impl/src/main/res/values-lt/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-lt/strings-autofill-impl.xml index bacc9406121d..e1a033915004 100644 --- a/autofill/autofill-impl/src/main/res/values-lt/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-lt/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Prisijungimams apsaugoti reikia naudoti prietaiso užrakinimo šabloną, PIN kodą arba biometrinius duomenis. Apsaugokite įrenginį, kad išsaugotumėte prisijungimus - Išsaugoti prisijungimą + Išsaugoti slaptažodį Išsaugoti slaptažodį - Ne dabar - Prisijungimai saugiai laikomi tik šiame įrenginyje, o juos galima tvarkyti nustatymuose, prisijungimų meniu. - Ar norite, kad „DuckDuckGo“ išsaugotų jūsų prisijungimą? + Neišsaugokite + Slaptažodžiai saugiai saugomi jūsų įrenginio meniu „Prisijungimai“. + Ar norite, kad „DuckDuckGo“ išsaugotų jūsų slaptažodį? Daugiau parinkčių - Naudoti išsaugotą prisijungimą? + Naudoti išsaugotą slaptažodį? „%1$s“ slaptažodis Svetainės slaptažodis Iš šios svetainės Iš %1$s Atnaujinti slaptažodį - Ne dabar + Neišsaugokite Atnaujinti naudotojo vardą? Atnaujinti naudotojo vardą Atnaujinti \n%1$s slaptažodį? - „DuckDuckGo“ atnaujins šį jūsų įrenginyje įrašytą prisijungimą. + „DuckDuckGo“ atnaujins šį jūsų įrenginyje išsaugotą slaptažodį. Uždaryti automatinio pildymo dialogo langą @@ -60,12 +60,12 @@ Prisijungimas paskutinį kartą atnaujintas %1$s Išsaugoti ir automatiškai pildyti prisijungimus + Prisijungimai saugiai laikomi jūsų įrenginyje Redaguoti Trinti Siūloma Dar nėra išsaugotų prisijungimų - Prisijungimai saugiai laikomi jūsų įrenginyje. Pridėti prisijungimą Ieškoti prisijungimų @@ -79,9 +79,9 @@ Naudotojo vardas nukopijuotas Slaptažodis nukopijuotas - Naudoti sugeneruotą slaptažodį iš „DuckDuckGo“? - Slaptažodis bus išsaugotas prisijungimų lange. - Naudoti sugeneruotą slaptažodį + Naudoti stiprų „DuckDuckGo“ slaptažodį? + Slaptažodžiai saugiai saugomi jūsų įrenginio meniu „Prisijungimai“. + Naudoti stiprų slaptažodį Sukurti savo Nepalaikomas įrenginys diff --git a/autofill/autofill-impl/src/main/res/values-lv/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-lv/strings-autofill-impl.xml index 9167aca3fd98..0a51a5eeee1f 100644 --- a/autofill/autofill-impl/src/main/res/values-lv/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-lv/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Lai aizsargātu jūsu pieteikšanās datus, ir nepieciešama ierīces bloķēšanas figūra, PIN kods vai biometriskā aizsardzība. Aizsargājiet savu ierīci, lai saglabātu pieteikšanās datus - Saglabāt pieteikšanās datus + Saglabāt paroli Saglabāt paroli - Ne tagad - Pieteikšanās dati tiek droši saglabāti tavā ierīcē, un tos var pārvaldīt no iestatījumu izvēlnes Pieteikšanās dati. - Vai vēlies, lai DuckDuckGo saglabātu tavus pieteikšanās datus? + Nesaglabāt + Paroles tiek droši saglabātas tavā ierīcē, izvēlnē Pieteikšanās dati. + Vai vēlies, lai DuckDuckGo saglabātu tavu paroli? Citas iespējas - Vai izmantot saglabātos pieteikšanās datus? + Izmantot saglabāto paroli? %1$s parole Parole vietnei No šīs vietnes No %1$s Atjaunināt paroli - Ne tagad + Nesaglabāt Atjaunināt lietotājvārdu? Atjaunināt lietotājvārdu Atjaunināt paroli lietotājvārdam \n%1$s? - DuckDuckGo atjauninās šos saglabātos pieteikšanās datus tavā ierīcē. + DuckDuckGo atjauninās tavā ierīcē saglabāto paroli. Aizvērt automātiskās aizpildes dialoglodziņu @@ -60,12 +60,12 @@ Pieteikšanās dati pēdējo reizi atjaunināti %1$s Saglabāt un automātiski aizpildīt pieteikšanās datus + Pieteikšanās dati tiek droši saglabāti tavā ierīcē Rediģēt Dzēst Ieteikts Nav saglabātu pieteikšanās datu - Pieteikšanās dati tiek droši saglabāti tavā ierīcē. Pievienot pieteikšanās datus Meklēt pieteikšanās datus @@ -79,9 +79,9 @@ Lietotājvārds nokopēts Parole nokopēta - Vai izmantot DuckDuckGo ģenerētu paroli? - Parole tiks saglabāta Pieteikšanās datos. - Izmantot ģenerētu paroli + Vai izmantot spēcīgu DuckDuckGo paroli? + Paroles tiek droši saglabātas tavā ierīcē, izvēlnē Pieteikšanās dati. + Izmantot spēcīgu paroli Izveidot savu Ierīce netiek atbalstīta diff --git a/autofill/autofill-impl/src/main/res/values-nb/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-nb/strings-autofill-impl.xml index 3d35df13f7e9..2b484e4825cc 100644 --- a/autofill/autofill-impl/src/main/res/values-nb/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-nb/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Du trenger låsemønster, PIN-kode eller biometri på enheten for å beskytte påloggingene dine. Sikre enheten din for å lagre pålogginger - Lagre påloggingen + Lagre passordet Lagre passordet - Ikke nå - Pålogginger lagres trygt på enheten din og kan administreres i påloggingsmenyen i innstillinger. - Vil du at DuckDuckGo skal lagre påloggingen din? + Ikke lagre + Passord lagres trygt på enheten din i påloggingsmenyen. + Vil du at DuckDuckGo skal lagre passordet ditt? Flere alternativer - Vil du bruke en lagret pålogging? + Vil du bruke et lagret passord? Passord for %1$s Passord for nettstedet Fra dette nettstedet Fra %1$s Oppdater passordet - Ikke nå + Ikke lagre Vil du oppdatere brukernavnet? Oppdater brukernavn Vil du oppdatere passordet for \n%1$s? - DuckDuckGo oppdaterer denne lagrede påloggingen på enheten din. + DuckDuckGo oppdaterer dette lagrede passordet på enheten din. Lukk Autofyll-dialogboksen @@ -60,12 +60,12 @@ Pålogging sist oppdatert %1$s Lagre og fyll inn pålogginger automatisk + Pålogginger lagres på enheten din på en sikker måte Rediger Slett Forslag Ingen pålogginger er lagret ennå - Pålogginger lagres på enheten din på en sikker måte. Legg til pålogging Søk i pålogginger @@ -79,9 +79,9 @@ Brukernavnet er kopiert Passordet er kopiert - Vil du bruke et generert passord fra DuckDuckGo? - Passord blir lagret i pålogginger. - Bruk generert passord + Vil du bruke et sterkt passord fra DuckDuckGo? + Passord lagres trygt på enheten din i påloggingsmenyen. + Bruk sterkt passord Opprett eget passord Enheten støttes ikke diff --git a/autofill/autofill-impl/src/main/res/values-nl/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-nl/strings-autofill-impl.xml index 1551e3545820..5a754fbb79e0 100644 --- a/autofill/autofill-impl/src/main/res/values-nl/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-nl/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Een vergrendelingspatroon, pincode of biometrische gegevens zijn noodzakelijk om je aanmeldgegevens te beschermen. Beveilig je apparaat om aanmeldgegevens op te slaan - Login opslaan + Wachtwoord opslaan Wachtwoord opslaan - Niet nu - Aanmeldgegevens worden veilig opgeslagen op je apparaat en kunnen worden beheerd via het menu Aanmeldgegevens in Instellingen. - Wil je dat DuckDuckGo je login opslaat? + Niet opslaan + Wachtwoorden worden veilig opgeslagen op je apparaat in het menu \'Aanmeldingen\'. + Wil je dat DuckDuckGo je wachtwoord opslaat? Meer opties - Opgeslagen aanmeldgegevens gebruiken? + Een opgeslagen wachtwoord gebruiken? Wachtwoord voor %1$s Wachtwoord voor website Van deze website Van %1$s Wachtwoord bijwerken - Niet nu + Niet opslaan Gebruikersnaam bijwerken? Gebruikersnaam bijwerken Wachtwoord bijwerken voor \n%1$s? - DuckDuckGo werkt deze opgeslagen aanmeldgegevens op je apparaat bij. + DuckDuckGo werkt dit opgeslagen wachtwoord op je apparaat bij. Dialoogvenster Automatisch invullen sluiten @@ -60,12 +60,12 @@ Aanmeldgegevens laatste bijgewerkt op %1$s Aanmeldgegevens opslaan en automatisch invullen + Aanmeldgegevens worden veilig opgeslagen op je apparaat Bewerken Verwijderen Aanbevolen Nog geen aanmeldgegevens opgeslagen - Aanmeldgegevens worden veilig opgeslagen op je apparaat. Aanmeldgegevens toevoegen Aanmeldgegevens zoeken @@ -79,9 +79,9 @@ Gebruikersnaam gekopieerd Wachtwoord gekopieerd - Gegenereerd wachtwoord van DuckDuckGo gebruiken? - Wachtwoord wordt opgeslagen in aanmeldgegevens. - Gegenereerd wachtwoord gebruiken + Een sterk wachtwoord van DuckDuckGo gebruiken? + Wachtwoorden worden veilig opgeslagen op je apparaat in het menu \'Aanmeldingen\'. + Sterk wachtwoord gebruiken Zelf wachtwoord aanmaken Apparaat wordt niet ondersteund diff --git a/autofill/autofill-impl/src/main/res/values-pl/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-pl/strings-autofill-impl.xml index c96ac6404a87..00cfeaca09c8 100644 --- a/autofill/autofill-impl/src/main/res/values-pl/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-pl/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Aby chronić dane logowania, korzystaj z blokady urządzenia za pomocą wzoru, kodu PIN lub danych biometrycznych. Zabezpiecz urządzenie, aby chronić dane logowania - Zapisz logowanie + Zapisz hasło Zapisz hasło - Nie teraz - Dane logowania są bezpiecznie przechowywane na Twoim urządzeniu i można nimi zarządzać w sekcji Loginy menu Ustawienia. - Czy chcesz zapisać dane logowania w DuckDuckGo? + Nie zapisuj + Hasła są bezpiecznie przechowywane na Twoim urządzeniu w menu Loginy. + Czy chcesz zapisać hasło w DuckDuckGo? Więcej opcji - Czy chcesz użyć zapisanego loginu? + Użyć zapisanego hasła? Hasło do witryny %1$s Hasło do witryny Z tej strony Z %1$s Aktualizuj hasło - Nie teraz + Nie zapisuj Zaktualizować nazwę użytkownika? Zaktualizuj nazwę użytkownika Zaktualizować hasło \n%1$s? - DuckDuckGo zaktualizuje ten zapisany login na Twoim urządzeniu. + DuckDuckGo zaktualizuje to zapisane hasło na Twoim urządzeniu. Zamknij okno dialogowe autouzupełniania @@ -60,12 +60,12 @@ Ostatnia aktualizacja loginu: %1$s Zapisz i autouzupełniaj loginy + Dane logowania są bezpiecznie przechowywane na Twoim urządzeniu Edytuj Usuń Sugerowane Nie zapisano jeszcze żadnych loginów - Dane logowania są bezpiecznie przechowywane na Twoim urządzeniu. Dodaj login Wyszukiwanie loginów @@ -79,9 +79,9 @@ Skopiowano nazwę użytkownika Skopiowano hasło - Użyć hasła wygenerowanego z DuckDuckGo? - Hasło zostanie zapisane w Danych logowania. - Użyj wygenerowanego hasła + Użyć silnego hasła z DuckDuckGo? + Hasła są bezpiecznie przechowywane na Twoim urządzeniu w menu Loginy. + Użyj silnego hasła Utwórz własne Urządzenie nie jest obsługiwane diff --git a/autofill/autofill-impl/src/main/res/values-pt/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-pt/strings-autofill-impl.xml index 757aa7d56a6c..08a9b6cbdfa6 100644 --- a/autofill/autofill-impl/src/main/res/values-pt/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-pt/strings-autofill-impl.xml @@ -23,27 +23,27 @@ É necessário um padrão de bloqueio de dispositivo, PIN ou biometria para proteger os teus inícios de sessão. Proteger o dispositivo para guardar inícios de sessão - Guardar início de sessão + Guardar palavra-passe Guardar palavra-passe - Agora não - Os inícios de sessão são armazenados com segurança no teu dispositivo, e podes efetuar a gestão dos mesmos a partir do menu Inícios de sessão nas Definições. - Queres que o DuckDuckGo guarde o teu início de sessão? + Não guardar + As palavras-passe são armazenadas de forma segura no teu dispositivo no menu Inícios de sessão. + Queres que o DuckDuckGo guarde a tua palavra-passe? Mais opções - Utilizar um início de sessão guardado? + Usar uma palavra-passe guardada? Palavra-passe de %1$s Palavra-passe do site Deste site De %1$s Atualizar palavra-passe - Agora não + Não guardar Atualizar nome de utilizador? Atualizar nome de utilizador Atualizar palavra-passe de \n%1$s? - O DuckDuckGo vai atualizar este início de sessão armazenado no teu dispositivo. + O DuckDuckGo vai atualizar esta palavra-passe armazenada no teu dispositivo. Fechar caixa de diálogo de preenchimento automático @@ -60,12 +60,12 @@ Dados de início de sessão atualizados pela última vez em %1$s Guardar e preencher dados de início de sessão automaticamente + Os inícios de sessão são armazenados com segurança no teu dispositivo Editar Eliminar Sugerido Nenhum início de sessão guardado - Os inícios de sessão são armazenados com segurança no teu dispositivo. Adicionar início de sessão Pesquisar inícios de sessão @@ -79,9 +79,9 @@ Nome de utilizador copiado Palavra-passe copiada - Utilizar palavra-passe gerada pelo DuckDuckGo? - A palavra-passe será guardada em Início de sessão. - Utilizar palavra-passe gerada + Utilizar uma palavra-passe forte do DuckDuckGo? + As palavras-passe são armazenadas de forma segura no teu dispositivo no menu Inícios de sessão. + Utilizar palavra-passe forte Criar a minha própria palavra-passe Dispositivo não suportado diff --git a/autofill/autofill-impl/src/main/res/values-ro/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-ro/strings-autofill-impl.xml index 589d0acb4d27..75915a3bd0c1 100644 --- a/autofill/autofill-impl/src/main/res/values-ro/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-ro/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Este necesar un model de blocare a dispozitivului, un cod PIN sau date biometrice pentru a-ți proteja conectările. Securizează-ți dispozitivul pentru a salva conectările - Salvează autentificarea + Salvează parola Salvează parola - Nu acum - Datele de conectare sunt stocate în siguranță pe dispozitivul tău și pot fi gestionate din Setări, meniul Date de conectare. - Vrei ca DuckDuckGo să îți salveze datele de conectare? + Nu salva + Parolele sunt stocate în siguranță pe dispozitivul dvs. în meniul Autentificări. + Dorești ca DuckDuckGo să-ți salveze parola? Mai multe opțiuni - Folosești datele de conectare salvate? + Folosești o parolă salvată? Parola pentru %1$s Parola pentru site De pe acest site web Din %1$s Actualizează parola - Nu acum + Nu salva Actualizezi numele de utilizator? Actualizează numele de utilizator Actualizezi parola pentru \n%1$s? - DuckDuckGo va actualiza aceste date de conectare stocate pe dispozitivul tău. + DuckDuckGo va actualiza această parolă stocată pe dispozitivul tău. Închide dialogul de completare automată @@ -60,12 +60,12 @@ Ultima actualizare a datelor de autentificare %1$s Salvează și completează automat datele de autentificare + Datele de conectare sunt stocate în siguranță pe dispozitivul tău Editează Ștergere Sugerat Nu au fost salvate încă date de autentificare - Datele de conectare sunt stocate în siguranță pe dispozitivul tău. Adaugă date de autentificare Date de autentificare pentru căutare @@ -79,9 +79,9 @@ Numele de utilizator a fost copiat Parolă copiată - Vrei să folosești parola generată de DuckDuckGo? - Parola va fi salvată în Datele de conectare. - Folosește parola generată + Folosiți o parolă puternică de la DuckDuckGo? + Parolele sunt stocate în siguranță pe dispozitivul dvs. în meniul Autentificări. + Utilizați o parolă puternică Îmi creez propria parolă Dispozitivul nu este acceptat diff --git a/autofill/autofill-impl/src/main/res/values-ru/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-ru/strings-autofill-impl.xml index 98c2795cf9cf..24f87cfb11ca 100644 --- a/autofill/autofill-impl/src/main/res/values-ru/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-ru/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Для защиты логинов требуются биометрические данные, графический ключ или ПИН-код. Защитите свое устройство, чтобы сохранять логины - Сохранить логин + Сохранить пароль Сохранить пароль - Не сейчас - Учетные данные надежно хранятся на вашем устройстве, а проконтролировать их можно в меню «Логины» в «Настройках». - Хотите, чтобы DuckDuckGo сохранил логин? + Не сохранять + Пароли надежно хранятся на вашем устройстве в меню «Логины». + Хотите, чтобы DuckDuckGo сохранил ваш пароль? Другие параметры - Использовать сохраненный логин? + Использовать сохраненный пароль? Пароль для %1$s Пароль для сайта С этого сайта С %1$s Обновить пароль - Не сейчас + Не сохранять Обновить имя пользователя? Обновить имя пользователя Обновить пароль \n%1$s? - DuckDuckGo обновит логин, сохраненный на вашем устройстве. + DuckDuckGo обновит пароль, сохраненный на вашем устройстве. Закрыть диалоговое окно автозаполнения @@ -60,12 +60,12 @@ Последнее обновление логина: %1$s Сохранить и заполнять автоматически + Учетные данные надежно хранятся на вашем устройстве Редактировать Удалить Рекомендации Сохраненных логинов пока нет - Учетные данные сохранены и защищены на вашем устройстве. Добавить логин Поиск логинов @@ -79,9 +79,9 @@ Имя пользователя скопировано Пароль скопирован - Использовать пароль, сгенерированный DuckDuckGo? - Пароль будет сохранен в разделе «Логины». - Использовать предложенный + Как насчет надежного пароля от DuckDuckGo? + Пароли надежно хранятся на вашем устройстве в меню «Логины». + Применить надежный пароль Создать собственный Устройство не поддерживается diff --git a/autofill/autofill-impl/src/main/res/values-sk/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-sk/strings-autofill-impl.xml index 800885b620d0..7b391a0815ff 100644 --- a/autofill/autofill-impl/src/main/res/values-sk/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-sk/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Na ochranu vašich prihlasovacích údajov sa vyžaduje vzor uzamknutia zariadenia, kód PIN alebo biometrické údaje. Zabezpečte svoje zariadenie a uložte prihlasovacie údaje - Uložiť prihlasovacie údaje + Uložiť heslo Uložiť heslo - Teraz nie - Prihlasovacie údaje sú zabezpečene uložené len v tomto zariadení a môžete ich spravovať v ponuke Prihlasovacie údaje v Nastaveniach. - Chcete, aby DuckDuckGo uložil vaše prihlasovacie údaje? + Neukladať + Heslá sú bezpečne uložené na vašom zariadení v ponuke Prihlásenia. + Chcete, aby DuckDuckGo uložil vaše heslo? Ďalšie možnosti - Použiť uložené prihlasovacie údaje? + Použiť uložené heslo? Heslo pre %1$s Heslo pre lokalitu Z tejto webovej lokality Z adresy %1$s Aktualizovať heslo - Teraz nie + Neukladať Aktualizovať meno používateľa? Aktualizovať používateľské meno Aktualizovať heslo pre \n%1$s? - DuckDuckGo bude aktualizovať tieto uložené prihlasovacie údaje vo vašom zariadení. + DuckDuckGo aktualizuje toto uložené heslo vo vašom zariadení. Zavrieť dialógové okno automatického vypĺňania @@ -60,12 +60,12 @@ Naposledy aktualizované prihlásenie %1$s Uložiť a automaticky vyplniť prihlásenia + Prihlasovacie údaje sú zabezpečeným spôsobom uložené vo vašom zariadení. Upraviť Vymazať Navrhované Zatiaľ nie sú uložené žiadne prihlásenia - Prihlasovacie údaje sú zabezpečeným spôsobom uložené vo vašom zariadení. Pridať prihlásenie Vyhľadávanie prihlásení @@ -79,9 +79,9 @@ Meno používateľa bolo skopírované Heslo bolo skopírované - Použiť vygenerované heslo z DuckDuckGo? - Heslo sa uloží do prihlasovacích údajov. - Použiť vygenerované heslo + Používate silné heslo od DuckDuckGo? + Heslá sú bezpečne uložené na vašom zariadení v ponuke Prihlásenia. + Používanie silného hesla Vytvoriť vlastné Nepodporované zariadenie diff --git a/autofill/autofill-impl/src/main/res/values-sl/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-sl/strings-autofill-impl.xml index 9da7bb6c24fb..0b360e5a5239 100644 --- a/autofill/autofill-impl/src/main/res/values-sl/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-sl/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Za zaščito prijav je potreben vzorec za zaklepanje naprave, koda PIN ali biometrični podatki. Zavarujte svojo napravo, če želite shraniti prijave - Shrani prijavo + Shrani geslo Shrani geslo - Ne zdaj - Prijave so varno shranjene v tej napravi in jih lahko upravljate v razdelku Prijave v nastavitvah. - Ali želite, da DuckDuckGo shrani vašo prijavo? + Ne shrani + Gesla so varno shranjena v napravi v meniju Prijave. + Ali želite, da DuckDuckGo shrani vaše geslo? Več možnosti - Želite uporabiti shranjene podatke za prijavo? + Želite uporabiti shranjeno geslo? Geslo za spletno mesto %1$s Geslo za spletno mesto S tega spletnega mesta S spletnega mesta %1$s Posodobitev gesla - Ne zdaj + Ne shrani Želite posodobiti uporabniško ime? Posodobi uporabniško ime Želite posodobiti geslo za \n%1$s? - DuckDuckGo bo posodobil to shranjeno prijavo v vaši napravi. + DuckDuckGo bo posodobil to shranjeno geslo v vaši napravi. Zapri pogovorno okno za samodejno izpolnjevanje @@ -60,12 +60,12 @@ Prijavni podatki nazadnje posodobljeni %1$s Shrani in samodejno izpolni prijavne podatke + Prijave so varno shranjene v vaši napravi. Uredi Izbriši Predlagano Podatki za prijavo še niso shranjeni - Prijave so varno shranjene v vaši napravi. Dodajanje podatkov za prijavo Iskanje podatkov za prijavo @@ -79,9 +79,9 @@ Uporabniško ime je bilo kopirano Geslo je bilo kopirano - Želite uporabiti geslo, ki ga je ustvaril DuckDuckGo? - Geslo bo shranjeno v razdelku Prijave. - Uporabi ustvarjeno geslo + Želite uporabiti močno geslo od DuckDuckGo? + Gesla so varno shranjena v napravi v meniju Prijave. + Uporabi močno geslo Ustvari svoje Naprava ni podprta diff --git a/autofill/autofill-impl/src/main/res/values-sv/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-sv/strings-autofill-impl.xml index 387f18ed4e30..2e6c244cd5a9 100644 --- a/autofill/autofill-impl/src/main/res/values-sv/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-sv/strings-autofill-impl.xml @@ -23,27 +23,27 @@ För att skydda dina inloggningar krävs ett enhetslåsmönster, en PIN-kod eller biometri. Säkra din enhet för att spara inloggningar - Spara inloggning + Spara lösenord Spara lösenord - Inte nu - Inloggningar lagras säkert på din enhet, och kan hanteras genom menyn Inloggningar i inställningarna. - Vill du att DuckDuckGo ska spara din inloggning? + Spara inte + Lösenord lagras säkert på din enhet i menyn Inloggningar. + Vill du att DuckDuckGo ska spara ditt lösenord? Fler alternativ - Använd sparad inloggning? + Vill du använda ett sparat lösenord? Lösenord för %1$s Lösenord för webbplatsen Från den här webbplatsen Från %1$s Uppdatera lösenord - Inte nu + Spara inte Uppdatera användarnamn? Uppdatera användarnamn Uppdatera lösenordet för \n%1$s? - DuckDuckGo uppdaterar den här lagrade inloggningen på din enhet. + DuckDuckGo kommer att uppdatera det lagrade lösenordet på din enhet. Stäng dialogrutan för automatisk ifyllning @@ -60,12 +60,12 @@ Inloggningen uppdaterades senast %1$s Spara och fyll i inloggningar automatiskt + Inloggningar lagras säkert på din enhet Redigera Ta bort Förslag Inga inloggningar sparade ännu - Inloggningar lagras säkert på din enhet. Lägg till inloggning Sök inloggningar @@ -79,9 +79,9 @@ Användarnamn kopierat Lösenord kopierat - Använd genererat lösenord från DuckDuckGo? - Lösenordet sparas i Inloggningar. - Använd genererat lösenord + Använd ett starkt lösenord från DuckDuckGo? + Lösenord lagras säkert på din enhet i menyn Inloggningar. + Använd starkt lösenord Skapa mitt eget Enheten stöds inte diff --git a/autofill/autofill-impl/src/main/res/values-tr/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values-tr/strings-autofill-impl.xml index 06413ab2f0d6..40ccdab42793 100644 --- a/autofill/autofill-impl/src/main/res/values-tr/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values-tr/strings-autofill-impl.xml @@ -23,27 +23,27 @@ Giriş bilgilerinizin güvenliğini sağlamak için bir cihaz kilidi deseni, PIN veya biyometrik veri gereklidir. Giriş bilgilerini kaydetmek için cihazınızın güvenliğini sağlayın - Girişi Kaydet + Şifre Kaydet Şifre Kaydet - Şimdi Değil - Giriş bilgileri yalnızca bu cihazda güvenli bir şekilde saklanır ve Ayarlar\'daki Giriş Bilgileri menüsünden yönetilebilir. - DuckDuckGo\'nun Girişinizi kaydetmesini istiyor musunuz? + Kaydetme + Şifreler cihazınızdaki Oturum Açma menüsünde güvenli bir şekilde saklanır. + DuckDuckGo\'nun şifrenizi kaydetmesini istiyor musunuz? Diğer Seçenekler - Kayıtlı Giriş kullanılsın mı? + Kayıtlı bir şifre mi kullanıyorsunuz? %1$s şifresi Site şifresi Bu Web Sitesinden %1$s adresinden Şifreyi Güncelle - Şimdi Değil + Kaydetme Kullanıcı adı güncellensin mi? Kullanıcı Adını Güncelle %1$s için şifre güncellensin mi? - DuckDuckGo, cihazınızdaki bu kayıtlı Giriş bilgisini güncelleyecektir. + DuckDuckGo bu kayıtlı şifreyi cihazınızda güncelleyecektir. Otomatik Doldurma İletişim Kutusunu Kapat @@ -60,12 +60,12 @@ Son giriş güncelleme: %1$s Girişleri Kaydet ve Otomatik Doldur + Giriş bilgileri cihazınızda güvenli bir şekilde saklanır Düzenle Sil Önerilen Henüz kaydedilmiş Giriş yok - Giriş bilgileri cihazınızda güvenli bir şekilde saklanır. Giriş Ekle Giriş ara @@ -79,9 +79,9 @@ Kullanıcı adı kopyalandı Şifre kopyalandı - DuckDuckGo\'dan oluşturulan şifre kullanılsın mı? - Şifre, Girişlere kaydedilecektir. - Oluşturulan Şifreyi Kullan + DuckDuckGo\'nun oluşturduğu güçlü bir şifre kullanmak ister misiniz? + Şifreler cihazınızdaki Oturum Açma menüsünde güvenli bir şekilde saklanır. + Güçlü Şifre Kullan Kendiminkini Oluştur Cihaz desteklenmiyor diff --git a/autofill/autofill-impl/src/main/res/values/autofill-impl-dimensions.xml b/autofill/autofill-impl/src/main/res/values/autofill-impl-dimensions.xml index a7a3c1359e3d..8d1305db35b8 100644 --- a/autofill/autofill-impl/src/main/res/values/autofill-impl-dimensions.xml +++ b/autofill/autofill-impl/src/main/res/values/autofill-impl-dimensions.xml @@ -17,4 +17,5 @@ 200dp 30dp + 480dp \ No newline at end of file diff --git a/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml index 8191c78f1143..fa69a67dcee3 100644 --- a/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml @@ -23,27 +23,27 @@ A device lock pattern, PIN, or biometrics is required to protect your Logins. Secure your device to save Logins - Save Login + Save Password Save Password - Not Now - Logins are stored securely on your device, and can be managed from the Logins menu in Settings. - Do you want DuckDuckGo to save your Login? + Don\'t Save + Passwords are stored securely on your device in the Logins menu. + Do you want DuckDuckGo to save your password? More Options - Use a saved Login? + Use a saved password? Password for %1$s Password for site From This Website From %1$s Update Password - Not Now + Don\'t Save Update username? Update Username Update password for \n%1$s? - DuckDuckGo will update this stored Login on your device. + DuckDuckGo will update this stored password on your device. Close Autofill Dialog @@ -60,12 +60,12 @@ Login last updated %1$s Save and Autofill Logins + Logins are stored securely on your device Edit Delete Suggested No Logins saved yet - Logins are stored securely on your device. Add Login Search logins @@ -79,9 +79,9 @@ Username copied Password copied - Use generated password from DuckDuckGo? - Password will be saved in Logins. - Use Generated Password + Use a strong password from DuckDuckGo? + Passwords are stored securely on your device in the Logins menu. + Use Strong Password Create My Own Device not supported diff --git a/autofill/autofill-impl/src/main/res/values/styles.xml b/autofill/autofill-impl/src/main/res/values/styles.xml index 4d35b6b4037c..3921ffce2e96 100644 --- a/autofill/autofill-impl/src/main/res/values/styles.xml +++ b/autofill/autofill-impl/src/main/res/values/styles.xml @@ -52,4 +52,18 @@ @drawable/ic_close_24 + + + + \ No newline at end of file diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillCapabilityCheckerImplTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillCapabilityCheckerImplTest.kt index 2469e30f7468..613e1a2f7d4c 100644 --- a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillCapabilityCheckerImplTest.kt +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillCapabilityCheckerImplTest.kt @@ -16,14 +16,13 @@ package com.duckduckgo.autofill.impl -import com.duckduckgo.autofill.api.AutofillFeature import com.duckduckgo.autofill.api.InternalTestUserChecker import com.duckduckgo.common.test.CoroutineTestRule -import com.duckduckgo.feature.toggles.api.Toggle -import com.duckduckgo.feature.toggles.api.Toggle.State +import com.duckduckgo.feature.toggles.api.toggle.AutofillTestFeature import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -import org.junit.Assert.* +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any @@ -136,13 +135,13 @@ class AutofillCapabilityCheckerImplTest { canGeneratePassword: Boolean = false, canAccessCredentialManagement: Boolean = false, ) { - val autofillFeature = AutofillTestFeature( - topLevelFeatureEnabled = topLevelFeatureEnabled, - canInjectCredentials = canInjectCredentials, - canSaveCredentials = canSaveCredentials, - canGeneratePassword = canGeneratePassword, - canAccessCredentialManagement = canAccessCredentialManagement, - ) + val autofillFeature = AutofillTestFeature().also { + it.topLevelFeatureEnabled = topLevelFeatureEnabled + it.canInjectCredentials = canInjectCredentials + it.canGeneratePassword = canGeneratePassword + it.canSaveCredentials = canSaveCredentials + it.canAccessCredentialManagement = canAccessCredentialManagement + } whenever(autofillGlobalCapabilityChecker.isSecureAutofillAvailable()).thenReturn(true) whenever(autofillGlobalCapabilityChecker.isAutofillEnabledByConfiguration(any())).thenReturn(true) @@ -157,64 +156,6 @@ class AutofillCapabilityCheckerImplTest { ) } - private class AutofillTestFeature( - private val topLevelFeatureEnabled: Boolean, - private val canInjectCredentials: Boolean, - private val canSaveCredentials: Boolean, - private val canGeneratePassword: Boolean, - private val canAccessCredentialManagement: Boolean, - ) : AutofillFeature { - override fun self(): Toggle { - return object : Toggle { - override fun isEnabled(): Boolean = topLevelFeatureEnabled - override fun setEnabled(state: State) {} - override fun getRawStoredState(): State? { - TODO("Not yet implemented") - } - } - } - - override fun canInjectCredentials(): Toggle { - return object : Toggle { - override fun isEnabled(): Boolean = canInjectCredentials - override fun setEnabled(state: State) {} - override fun getRawStoredState(): State? { - TODO("Not yet implemented") - } - } - } - - override fun canSaveCredentials(): Toggle { - return object : Toggle { - override fun isEnabled(): Boolean = canSaveCredentials - override fun setEnabled(state: State) {} - override fun getRawStoredState(): State? { - TODO("Not yet implemented") - } - } - } - - override fun canGeneratePasswords(): Toggle { - return object : Toggle { - override fun isEnabled(): Boolean = canGeneratePassword - override fun setEnabled(state: State) {} - override fun getRawStoredState(): State? { - TODO("Not yet implemented") - } - } - } - - override fun canAccessCredentialManagement(): Toggle { - return object : Toggle { - override fun isEnabled(): Boolean = canAccessCredentialManagement - override fun setEnabled(state: State) {} - override fun getRawStoredState(): State? { - TODO("Not yet implemented") - } - } - } - } - companion object { private const val URL = "https://example.com" } diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillStoredBackJavascriptInterfaceTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillStoredBackJavascriptInterfaceTest.kt index 9c5901a40bb4..f05ebc8aedfa 100644 --- a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillStoredBackJavascriptInterfaceTest.kt +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/AutofillStoredBackJavascriptInterfaceTest.kt @@ -27,6 +27,7 @@ import com.duckduckgo.autofill.api.email.EmailManager import com.duckduckgo.autofill.api.passwordgeneration.AutomaticSavedLoginsMonitor import com.duckduckgo.autofill.api.store.AutofillStore import com.duckduckgo.autofill.impl.AutofillStoredBackJavascriptInterface.UrlProvider +import com.duckduckgo.autofill.impl.deduper.AutofillLoginDeduplicator import com.duckduckgo.autofill.impl.email.incontext.availability.EmailProtectionInContextRecentInstallChecker import com.duckduckgo.autofill.impl.email.incontext.store.EmailProtectionInContextDataStore import com.duckduckgo.autofill.impl.jsbridge.AutofillMessagePoster @@ -40,6 +41,7 @@ import com.duckduckgo.autofill.impl.jsbridge.request.SupportedAutofillInputSubTy import com.duckduckgo.autofill.impl.jsbridge.request.SupportedAutofillTriggerType.USER_INITIATED import com.duckduckgo.autofill.impl.jsbridge.response.AutofillResponseWriter import com.duckduckgo.autofill.impl.sharedcreds.ShareableCredentials +import com.duckduckgo.autofill.impl.systemautofill.SystemAutofillServiceSuppressor import com.duckduckgo.autofill.impl.ui.credential.passwordgeneration.Actions import com.duckduckgo.autofill.impl.ui.credential.passwordgeneration.AutogeneratedPasswordEventResolver import com.duckduckgo.common.test.CoroutineTestRule @@ -75,6 +77,8 @@ class AutofillStoredBackJavascriptInterfaceTest { private val inContextDataStore: EmailProtectionInContextDataStore = mock() private val recentInstallChecker: EmailProtectionInContextRecentInstallChecker = mock() private val testWebView = WebView(getApplicationContext()) + private val loginDeduplicator: AutofillLoginDeduplicator = NoopDeduplicator() + private val systemAutofillServiceSuppressor: SystemAutofillServiceSuppressor = mock() private lateinit var testee: AutofillStoredBackJavascriptInterface private val testCallback = TestCallback() @@ -99,6 +103,8 @@ class AutofillStoredBackJavascriptInterfaceTest { emailManager = emailManager, inContextDataStore = inContextDataStore, recentInstallChecker = recentInstallChecker, + loginDeduplicator = loginDeduplicator, + systemAutofillServiceSuppressor = systemAutofillServiceSuppressor, ) testee.callback = testCallback testee.webView = testWebView @@ -390,4 +396,8 @@ class AutofillStoredBackJavascriptInterfaceTest { // no-op } } + + private class NoopDeduplicator : AutofillLoginDeduplicator { + override fun deduplicate(originalUrl: String, logins: List): List = logins + } } diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillLoginDeduplicatorTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillLoginDeduplicatorTest.kt new file mode 100644 index 000000000000..84a7de98da12 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillLoginDeduplicatorTest.kt @@ -0,0 +1,165 @@ +package com.duckduckgo.autofill.impl.deduper + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.autofill.impl.encoding.TestUrlUnicodeNormalizer +import com.duckduckgo.autofill.impl.urlmatcher.AutofillDomainNameUrlMatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class RealAutofillLoginDeduplicatorTest { + private val urlMatcher = AutofillDomainNameUrlMatcher(TestUrlUnicodeNormalizer()) + private val matchTypeDetector = RealAutofillDeduplicationMatchTypeDetector(urlMatcher) + private val testee = RealAutofillLoginDeduplicator( + usernamePasswordMatcher = RealAutofillDeduplicationUsernameAndPasswordMatcher(), + bestMatchFinder = RealAutofillDeduplicationBestMatchFinder( + urlMatcher = urlMatcher, + matchTypeDetector = matchTypeDetector, + ), + ) + + @Test + fun whenEmptyListInThenEmptyListOut() = runTest { + val result = testee.deduplicate("example.com", emptyList()) + assertTrue(result.isEmpty()) + } + + @Test + fun whenSingleEntryInThenSingleEntryReturned() { + val inputList = listOf( + aLogin("domain", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + } + + @Test + fun whenEntriesCompletelyUnrelatedThenNoDeduplication() { + val inputList = listOf( + aLogin("domain_A", "username_A", "password_A"), + aLogin("domain_B", "username_B", "password_B"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(2, result.size) + assertNotNull(result.find { it.domain == "domain_A" }) + assertNotNull(result.find { it.domain == "domain_B" }) + } + + @Test + fun whenEntriesShareUsernameAndPasswordButNotDomainThenDeduped() { + val inputList = listOf( + aLogin("foo.com", "username", "password"), + aLogin("bar.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + } + + @Test + fun whenEntriesShareDomainAndUsernameButNotPasswordThenNoDeduplication() { + val inputList = listOf( + aLogin("example.com", "username", "123"), + aLogin("example.com", "username", "xyz"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(2, result.size) + assertNotNull(result.find { it.password == "123" }) + assertNotNull(result.find { it.password == "xyz" }) + } + + @Test + fun whenEntriesShareDomainAndPasswordButNotUsernameThenNoDeduplication() { + val inputList = listOf( + aLogin("example.com", "user_A", "password"), + aLogin("example.com", "user_B", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(2, result.size) + assertNotNull(result.find { it.username == "user_A" }) + assertNotNull(result.find { it.username == "user_B" }) + } + + @Test + fun whenEntriesShareMultipleCredentialsWhichArePerfectDomainMatchesThenDeduped() { + val inputList = listOf( + aLogin("example.com", "username", "password"), + aLogin("example.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + } + + @Test + fun whenEntriesShareMultipleCredentialsWhichArePartialDomainMatchesThenDeduped() { + val inputList = listOf( + aLogin("a.example.com", "username", "password"), + aLogin("b.example.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + } + + @Test + fun whenEntriesShareMultipleCredentialsWhichAreNotDomainMatchesThenDeduped() { + val inputList = listOf( + aLogin("foo.com", "username", "password"), + aLogin("bar.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + } + + @Test + fun whenEntriesShareCredentialsAcrossPerfectAndPartialMatchesThenDedupedToPerfectMatch() { + val inputList = listOf( + aLogin("example.com", "username", "password"), + aLogin("a.example.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + assertNotNull(result.find { it.domain == "example.com" }) + } + + @Test + fun whenEntriesShareCredentialsAcrossPerfectAndNonDomainMatchesThenDedupedToPerfectMatch() { + val inputList = listOf( + aLogin("example.com", "username", "password"), + aLogin("bar.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + assertNotNull(result.find { it.domain == "example.com" }) + } + + @Test + fun whenEntriesShareCredentialsAcrossPartialAndNonDomainMatchesThenDedupedToPerfectMatch() { + val inputList = listOf( + aLogin("a.example.com", "username", "password"), + aLogin("bar.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + assertNotNull(result.find { it.domain == "a.example.com" }) + } + + @Test + fun whenEntriesShareCredentialsAcrossPerfectAndPartialAndNonDomainMatchesThenDedupedToPerfectMatch() { + val inputList = listOf( + aLogin("a.example.com", "username", "password"), + aLogin("example.com", "username", "password"), + aLogin("bar.com", "username", "password"), + ) + val result = testee.deduplicate("example.com", inputList) + assertEquals(1, result.size) + assertNotNull(result.find { it.domain == "example.com" }) + } + + private fun aLogin(domain: String, username: String, password: String): LoginCredentials { + return LoginCredentials(username = username, password = password, domain = domain) + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillMatchTypeDetectorTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillMatchTypeDetectorTest.kt new file mode 100644 index 000000000000..19a35dcb698c --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealAutofillMatchTypeDetectorTest.kt @@ -0,0 +1,53 @@ +package com.duckduckgo.autofill.impl.deduper + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.autofill.impl.deduper.AutofillDeduplicationMatchTypeDetector.MatchType +import com.duckduckgo.autofill.impl.encoding.TestUrlUnicodeNormalizer +import com.duckduckgo.autofill.impl.urlmatcher.AutofillDomainNameUrlMatcher +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RealAutofillMatchTypeDetectorTest { + private val testee = RealAutofillDeduplicationMatchTypeDetector(AutofillDomainNameUrlMatcher(TestUrlUnicodeNormalizer())) + + @Test + fun whenExactUrlMatchThenTypeIsPerfectMatch() { + val result = testee.detectMatchType("example.com", creds("example.com")) + result.assertIsPerfectMatch() + } + + @Test + fun whenSubdomainMatchOnSavedSiteThenTypeIsPartialMatch() { + val result = testee.detectMatchType("example.com", creds("subdomain.example.com")) + result.assertIsPartialMatch() + } + + @Test + fun whenSubdomainMatchOnVisitedSiteThenTypeIsPartialMatch() { + val result = testee.detectMatchType("subdomain.example.com", creds("example.com")) + result.assertIsPartialMatch() + } + + @Test + fun whenSubdomainMatchOnBothVisitedAndSavedSiteThenTypeIsPerfectMatch() { + val result = testee.detectMatchType("subdomain.example.com", creds("subdomain.example.com")) + result.assertIsPerfectMatch() + } + + @Test + fun whenNoETldPlusOneMatchNotAMatch() { + val result = testee.detectMatchType("foo.com", creds("example.com")) + result.assertNotAMatch() + } + + private fun MatchType.assertIsPerfectMatch() = assertTrue(this is MatchType.PerfectMatch) + private fun MatchType.assertIsPartialMatch() = assertTrue(this is MatchType.PartialMatch) + private fun MatchType.assertNotAMatch() = assertTrue(this is MatchType.NotAMatch) + + private fun creds(domain: String): LoginCredentials { + return LoginCredentials(domain = domain, username = "", password = "") + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorBestMatchFinderTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorBestMatchFinderTest.kt new file mode 100644 index 000000000000..8d7495c1096b --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorBestMatchFinderTest.kt @@ -0,0 +1,88 @@ +package com.duckduckgo.autofill.impl.deduper + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import com.duckduckgo.autofill.impl.encoding.TestUrlUnicodeNormalizer +import com.duckduckgo.autofill.impl.urlmatcher.AutofillDomainNameUrlMatcher +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RealLoginDeduplicatorBestMatchFinderTest { + + private val urlMatcher = AutofillDomainNameUrlMatcher(TestUrlUnicodeNormalizer()) + private val matchTypeDetector = RealAutofillDeduplicationMatchTypeDetector(urlMatcher) + private val testee = RealAutofillDeduplicationBestMatchFinder( + urlMatcher = urlMatcher, + matchTypeDetector = matchTypeDetector, + ) + + @Test + fun whenEmptyListThenNoBestMatchFound() { + assertNull(testee.findBestMatch("", emptyList())) + } + + @Test + fun whenSinglePerfectMatchThenThatIsReturnedAsBestMatch() { + val input = listOf( + LoginCredentials(id = 0, domain = "example.com", username = "username", password = "password"), + ) + val result = testee.findBestMatch("example.com", input) + assertNotNull(result) + } + + @Test + fun whenMultiplePerfectMatchesMostRecentlyModifiedIsReturned() { + val input = listOf( + creds("example.com", 1000), + creds("example.com", 2000), + ) + val result = testee.findBestMatch("example.com", input) + assertEquals(2000L, result!!.lastUpdatedMillis) + } + + @Test + fun whenMultiplePartialMatchesWithSameTimestampThenDomainAlphabeticallyFirstReturned() { + val input = listOf( + creds("a.example.com", 2000), + creds("b.example.com", 2000), + ) + val result = testee.findBestMatch("example.com", input) + assertEquals("a.example.com", result!!.domain) + } + + @Test + fun whenSingleNonMatchThenReturnedAsBestMatch() { + val input = listOf( + creds("not-a-match.com", 2000), + ) + val result = testee.findBestMatch("example.com", input) + assertEquals("not-a-match.com", result!!.domain) + } + + @Test + fun whenMultipleNonMatchesThenMostRecentlyModifiedIsReturned() { + val input = listOf( + creds("not-a-match.com", 2000), + creds("also-not-a-match.com", 1000), + ) + val result = testee.findBestMatch("example.com", input) + assertEquals("not-a-match.com", result!!.domain) + } + + @Test + fun whenMatchesFromAllTypesThenMatchInPerfectReturnedRegardlessOfTimestamps() { + val input = listOf( + creds("perfect-match.com", 1000), + creds("imperfect-match.com", 3000), + creds("not-a-match.com", 2000), + ) + val result = testee.findBestMatch("perfect-match.com", input) + assertEquals("perfect-match.com", result!!.domain) + } + + private fun creds(domain: String, lastModified: Long?): LoginCredentials { + return LoginCredentials(domain = domain, lastUpdatedMillis = lastModified, username = "", password = "") + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorUsernameAndPasswordMatcherTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorUsernameAndPasswordMatcherTest.kt new file mode 100644 index 000000000000..08ec6cf0d8f2 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginDeduplicatorUsernameAndPasswordMatcherTest.kt @@ -0,0 +1,70 @@ +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import org.junit.Assert.* +import org.junit.Test + +class RealLoginDeduplicatorUsernameAndPasswordMatcherTest { + private val testee = RealAutofillDeduplicationUsernameAndPasswordMatcher() + + @Test + fun whenEmptyListInThenEmptyListOut() { + val input = emptyList() + val output = testee.groupDuplicateCredentials(input) + assertTrue(output.isEmpty()) + } + + @Test + fun whenSingleEntryInThenSingleEntryOut() { + val input = listOf( + creds("username", "password"), + ) + val output = testee.groupDuplicateCredentials(input) + assertEquals(1, output.size) + } + + @Test + fun whenMultipleEntriesWithNoDuplicationAtAllThenNumberOfGroupsReturnedMatchesNumberOfEntriesInputted() { + val input = listOf( + creds("username_a", "password_x"), + creds("username_b", "password_y"), + creds("username_c", "password_z"), + ) + val output = testee.groupDuplicateCredentials(input) + assertEquals(3, output.size) + } + + @Test + fun whenEntriesMatchOnUsernameButNotPasswordThenNotGrouped() { + val input = listOf( + creds("username", "password_x"), + creds("username", "password_y"), + ) + val output = testee.groupDuplicateCredentials(input) + assertEquals(2, output.size) + } + + @Test + fun whenEntriesMatchOnPasswordButNotUsernameThenNotGrouped() { + val input = listOf( + creds("username_a", "password"), + creds("username_b", "password"), + ) + val output = testee.groupDuplicateCredentials(input) + assertEquals(2, output.size) + } + + @Test + fun whenEntriesMatchOnUsernameAndPasswordThenGrouped() { + val input = listOf( + creds("username", "password"), + creds("username", "password"), + ) + val output = testee.groupDuplicateCredentials(input) + assertEquals(1, output.size) + } + + private fun creds(username: String, password: String): LoginCredentials { + return LoginCredentials(username = username, password = password, domain = "domain") + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplicationTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplicationTest.kt new file mode 100644 index 000000000000..6d436ade1f48 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/deduper/RealLoginSorterForDeduplicationTest.kt @@ -0,0 +1,80 @@ +package com.duckduckgo.autofill.impl.deduper + +import com.duckduckgo.autofill.api.domain.app.LoginCredentials +import org.junit.Assert.* +import org.junit.Test + +class RealLoginSorterForDeduplicationTest { + + private val testee = AutofillDeduplicationLoginComparator() + + @Test + fun whenFirstLoginIsNewerThenReturnNegative() { + val login1 = creds(lastUpdated = 2000, domain = null) + val login2 = creds(lastUpdated = 1000, domain = null) + assertTrue(testee.compare(login1, login2) < 0) + } + + @Test + fun whenSecondLoginIsNewerThenReturnPositive() { + val login1 = creds(lastUpdated = 1000, domain = null) + val login2 = creds(lastUpdated = 2000, domain = null) + assertTrue(testee.compare(login1, login2) > 0) + } + + @Test + fun whenFirstLoginHasNoLastModifiedTimestampThenReturnsNegative() { + val login1 = creds(lastUpdated = null, domain = null) + val login2 = creds(lastUpdated = 2000, domain = null) + assertTrue(testee.compare(login1, login2) < 0) + } + + @Test + fun whenSecondLoginHasNoLastModifiedTimestampThenReturnsPositive() { + val login1 = creds(lastUpdated = 1000, domain = null) + val login2 = creds(lastUpdated = null, domain = null) + assertTrue(testee.compare(login1, login2) > 0) + } + + @Test + fun whenLastModifiedTimesEqualAndFirstLoginDomainShouldBeSortedFirstThenReturnsNegative() { + val login1 = creds(lastUpdated = 1000, domain = "example.com") + val login2 = creds(lastUpdated = 1000, domain = "site.com") + assertTrue(testee.compare(login1, login2) < 0) + } + + @Test + fun whenLastModifiedTimesEqualAndSecondLoginDomainShouldBeSortedFirstThenReturnsNegative() { + val login1 = creds(lastUpdated = 1000, domain = "site.com") + val login2 = creds(lastUpdated = 1000, domain = "example.com") + assertTrue(testee.compare(login1, login2) > 0) + } + + @Test + fun whenLastModifiedTimesEqualAndDomainsEqualThenReturns0() { + val login1 = creds(lastUpdated = 1000, domain = "example.com") + val login2 = creds(lastUpdated = 1000, domain = "example.com") + assertEquals(0, testee.compare(login1, login2)) + } + + @Test + fun whenLastModifiedDatesMissingAndDomainMissingThenReturns0() { + val login1 = creds(lastUpdated = null, domain = null) + val login2 = creds(lastUpdated = null, domain = null) + assertEquals(0, testee.compare(login1, login2)) + } + + @Test + fun whenLoginsSameLastUpdatedTimeThenReturn0() { + val login1 = creds(lastUpdated = 1000, domain = null) + val login2 = creds(lastUpdated = 1000, domain = null) + assertEquals(0, testee.compare(login1, login2)) + } + + private fun creds( + lastUpdated: Long?, + domain: String? = "example.com", + ): LoginCredentials { + return LoginCredentials(id = 0, lastUpdatedMillis = lastUpdated, domain = domain, username = "username", password = "password") + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/email/incontext/availability/RealEmailProtectionInContextAvailabilityRulesTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/email/incontext/availability/RealEmailProtectionInContextAvailabilityRulesTest.kt index 3cf632e6ed9c..5ba009654b35 100644 --- a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/email/incontext/availability/RealEmailProtectionInContextAvailabilityRulesTest.kt +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/email/incontext/availability/RealEmailProtectionInContextAvailabilityRulesTest.kt @@ -7,7 +7,7 @@ import com.duckduckgo.autofill.impl.email.incontext.EmailProtectionInContextSign import com.duckduckgo.autofill.impl.email.remoteconfig.EmailProtectionInContextExceptions import com.duckduckgo.common.test.CoroutineTestRule import com.duckduckgo.feature.toggles.api.Toggle -import com.duckduckgo.feature.toggles.api.Toggle.State +import com.duckduckgo.feature.toggles.api.toggle.TestToggle import java.util.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -115,12 +115,6 @@ class RealEmailProtectionInContextAvailabilityRulesTest { override fun self(): Toggle = TestToggle(enabled) } - private open class TestToggle(val enabled: Boolean) : Toggle { - override fun getRawStoredState(): State? = null - override fun setEnabled(state: State) {} - override fun isEnabled() = enabled - } - companion object { private const val ALLOWED_URL = "https://duckduckgo.com" private const val DISALLOWED_URL = "https://example.com" diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptorTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptorTest.kt new file mode 100644 index 000000000000..fd71e35e5184 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelInterceptorTest.kt @@ -0,0 +1,57 @@ +package com.duckduckgo.autofill.impl.pixel + +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SAVE_LOGIN_PROMPT_SHOWN +import com.duckduckgo.autofill.impl.pixel.AutofillPixelParameters.AUTOFILL_DEFAULT_STATE +import com.duckduckgo.autofill.store.AutofillPrefsStore +import com.duckduckgo.common.test.api.FakeChain +import okhttp3.HttpUrl +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class AutofillPixelInterceptorTest { + + private val autofillStore: AutofillPrefsStore = mock() + private val testee: AutofillPixelInterceptor = AutofillPixelInterceptor(autofillStore) + + @Test + fun whenNotAMatchThenParamsNotAdded() { + val url = intercept(NON_MATCHING_PIXEL) + assertNull(url.queryParameter(MATCHING_PIXEL)) + } + + @Test + fun whenMatchingPixelAndDefaultStateWasDisabledThenCorrectParamsAdded() { + configureDefaultEnabledState(false) + val url = intercept(MATCHING_PIXEL) + val parameter = url.queryParameter(AUTOFILL_DEFAULT_STATE) + assertEquals("off", parameter) + } + + @Test + fun whenMatchingPixelAndDefaultStateWasEnabledThenCorrectParamsAdded() { + configureDefaultEnabledState(true) + val url = intercept(MATCHING_PIXEL) + val parameter = url.queryParameter(AUTOFILL_DEFAULT_STATE) + assertEquals("on", parameter) + } + + private fun configureDefaultEnabledState(defaultEnabledState: Boolean) { + whenever(autofillStore.wasDefaultStateEnabled()).thenReturn(defaultEnabledState) + } + + private fun intercept(pixelName: String): HttpUrl { + val pixelUrl = String.format(PIXEL_TEMPLATE, pixelName) + return testee.intercept(FakeChain(pixelUrl)).request.url + } + + companion object { + private const val PIXEL_TEMPLATE = "https://improving.duckduckgo.com/t/%s_android_phone?" + + // any arbitrary pixel which is defined in the interceptor + private val MATCHING_PIXEL = AUTOFILL_SAVE_LOGIN_PROMPT_SHOWN.pixelName + private const val NON_MATCHING_PIXEL = "m_not_a_match" + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModelTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModelTest.kt index 6249d6db7116..28a1dc83c1b9 100644 --- a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModelTest.kt +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModelTest.kt @@ -19,10 +19,15 @@ package com.duckduckgo.autofill.impl.ui.credential.management import app.cash.turbine.test import com.duckduckgo.app.browser.favicon.FaviconManager import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.app.statistics.pixels.Pixel.PixelName import com.duckduckgo.autofill.api.domain.app.LoginCredentials import com.duckduckgo.autofill.api.email.EmailManager import com.duckduckgo.autofill.api.store.AutofillStore import com.duckduckgo.autofill.impl.deviceauth.DeviceAuthenticator +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.MENU_ACTION_AUTOFILL_PRESSED +import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.SETTINGS_AUTOFILL_MANAGEMENT_OPENED import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.Command import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.Command.ExitCredentialMode import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.Command.ExitListMode @@ -56,6 +61,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -117,6 +123,18 @@ class AutofillSettingsViewModelTest { } } + @Test + fun whenUserEnablesAutofillThenCorrectPixelFired() { + testee.onEnableAutofill() + verify(pixel).fire(AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED) + } + + @Test + fun whenUserDisablesAutofillThenCorrectPixelFired() { + testee.onDisableAutofill() + verify(pixel).fire(AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED) + } + @Test fun whenUserCopiesPasswordThenCommandIssuedToShowChange() = runTest { testee.onCopyPassword("hello") @@ -583,6 +601,38 @@ class AutofillSettingsViewModelTest { } } + @Test + fun whenScreenLaunchedDirectlyIntoCredentialViewThenNoLaunchPixelSent() { + val launchedFromBrowser = false + val directLinkToCredentials = true + testee.sendLaunchPixel(launchedFromBrowser, directLinkToCredentials) + verify(pixel, never()).fire(any(), any(), any()) + } + + @Test + fun whenScreenLaunchedDirectlyIntoCredentialViewAndLaunchedFromBrowserThenNoLaunchPixelSent() { + val launchedFromBrowser = true + val directLinkToCredentials = true + testee.sendLaunchPixel(launchedFromBrowser, directLinkToCredentials) + verify(pixel, never()).fire(any(), any(), any()) + } + + @Test + fun whenScreenLaunchedFromBrowserAndNotDirectLinkThenCorrectLaunchPixelSent() { + val launchedFromBrowser = true + val directLinkToCredentials = false + testee.sendLaunchPixel(launchedFromBrowser, directLinkToCredentials) + verify(pixel).fire(eq(MENU_ACTION_AUTOFILL_PRESSED), any(), any()) + } + + @Test + fun whenScreenLaunchedNotFromBrowserAndNotDirectLinkThenCorrectLaunchPixelSent() { + val launchedFromBrowser = false + val directLinkToCredentials = false + testee.sendLaunchPixel(launchedFromBrowser, directLinkToCredentials) + verify(pixel).fire(eq(SETTINGS_AUTOFILL_MANAGEMENT_OPENED), any(), any()) + } + private suspend fun configureStoreToHaveThisManyCredentialsStored(value: Int) { whenever(mockStore.getCredentialCount()).thenReturn(flowOf(value)) } diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounterTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounterTest.kt index 89f7fd0ecece..2d37e704aca7 100644 --- a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounterTest.kt +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/impl/ui/credential/saving/declines/AutofillDisablingDeclineCounterTest.kt @@ -146,7 +146,7 @@ class AutofillDisablingDeclineCounterTest { } private fun configureGlobalDeclineCountAtThreshold() { - whenever(autofillStore.autofillDeclineCount).thenReturn(3) + whenever(autofillStore.autofillDeclineCount).thenReturn(2) } private fun assertDeclineNotRecorded() { diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/RealAutofillPrefsStoreTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/RealAutofillPrefsStoreTest.kt new file mode 100644 index 000000000000..812ce89cdda3 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/RealAutofillPrefsStoreTest.kt @@ -0,0 +1,70 @@ +package com.duckduckgo.autofill.store + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.duckduckgo.autofill.store.feature.AutofillDefaultStateDecider +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class RealAutofillPrefsStoreTest { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val defaultStateDecider: AutofillDefaultStateDecider = mock() + + private val testee = RealAutofillPrefsStore( + applicationContext = context, + defaultStateDecider = defaultStateDecider, + ) + + @Test + fun whenAutofillStateNeverSetManuallyThenDefaultStateDeciderUsed() { + testee.isEnabled + verify(defaultStateDecider).defaultState() + } + + @Test + fun whenAutofillStateWasManuallySetToEnabledThenDefaultStateDeciderNotUsed() { + testee.isEnabled = true + testee.isEnabled + verify(defaultStateDecider, never()).defaultState() + } + + @Test + fun whenAutofillStateWasManuallySetToDisabledThenDefaultStateDeciderNotUsed() { + testee.isEnabled = false + testee.isEnabled + verify(defaultStateDecider, never()).defaultState() + } + + @Test + fun whenDeterminedEnabledByDefaultOnceThenNotDecidedAgain() { + // first call will decide default state should be enabled + whenever(defaultStateDecider.defaultState()).thenReturn(true) + assertTrue(testee.isEnabled) + verify(defaultStateDecider).defaultState() + + // second call should not invoke decider again + assertTrue(testee.isEnabled) + verifyNoMoreInteractions(defaultStateDecider) + } + + @Test + fun whenDeterminedNotEnabledByDefaultOnceThenWillCallToDeciderAgain() { + // first call will decide default state should not be enabled + whenever(defaultStateDecider.defaultState()).thenReturn(false) + assertFalse(testee.isEnabled) + + // second call should invoke decider again + assertFalse(testee.isEnabled) + verify(defaultStateDecider, times(2)).defaultState() + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/feature/RealAutofillDefaultStateDeciderTest.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/feature/RealAutofillDefaultStateDeciderTest.kt new file mode 100644 index 000000000000..2d9ae1b24003 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/autofill/store/feature/RealAutofillDefaultStateDeciderTest.kt @@ -0,0 +1,70 @@ +package com.duckduckgo.autofill.store.feature + +import com.duckduckgo.autofill.api.InternalTestUserChecker +import com.duckduckgo.browser.api.UserBrowserProperties +import com.duckduckgo.feature.toggles.api.toggle.AutofillTestFeature +import org.junit.Assert.* +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class RealAutofillDefaultStateDeciderTest { + + private val userBrowserProperties: UserBrowserProperties = mock() + private val autofillFeature = AutofillTestFeature() + private val internalTestUserChecker: InternalTestUserChecker = mock() + private val testee = RealAutofillDefaultStateDecider( + userBrowserProperties = userBrowserProperties, + autofillFeature = autofillFeature, + internalTestUserChecker = internalTestUserChecker, + ) + + @Test + fun whenRemoteFeatureDisabledThenNumberOfDaysInstalledIsIrrelevant() { + configureRemoteFeatureEnabled(false) + + configureDaysInstalled(0) + assertFalse(testee.defaultState()) + + configureDaysInstalled(1000) + assertFalse(testee.defaultState()) + } + + @Test + fun whenNumberOfDaysInstalledIsNotZeroThenFeatureFlagIsIrrelevant() { + configureDaysInstalled(0) + + configureRemoteFeatureEnabled(false) + assertFalse(testee.defaultState()) + + configureRemoteFeatureEnabled(false) + assertFalse(testee.defaultState()) + } + + @Test + fun whenInternalTesterThenAlwaysEnabledByDefault() { + configureDaysInstalled(100) + configureRemoteFeatureEnabled(false) + configureAsInternalTester() + assertTrue(testee.defaultState()) + } + + @Test + fun whenInstalledSameDayAndFeatureFlagEnabledThenEnabledByDefault() { + configureDaysInstalled(0) + configureRemoteFeatureEnabled(true) + assertTrue(testee.defaultState()) + } + + private fun configureAsInternalTester() { + whenever(internalTestUserChecker.isInternalTestUser).thenReturn(true) + } + + private fun configureRemoteFeatureEnabled(enabled: Boolean) { + autofillFeature.onByDefault = enabled + } + + private fun configureDaysInstalled(daysInstalled: Long) { + whenever(userBrowserProperties.daysSinceInstalled()).thenReturn(daysInstalled) + } +} diff --git a/autofill/autofill-impl/src/test/java/com/duckduckgo/feature/toggles/api/toggle/AutofillTestFeature.kt b/autofill/autofill-impl/src/test/java/com/duckduckgo/feature/toggles/api/toggle/AutofillTestFeature.kt new file mode 100644 index 000000000000..f45819c6c1a4 --- /dev/null +++ b/autofill/autofill-impl/src/test/java/com/duckduckgo/feature/toggles/api/toggle/AutofillTestFeature.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.feature.toggles.api.toggle + +import com.duckduckgo.autofill.api.AutofillFeature +import com.duckduckgo.feature.toggles.api.Toggle + +class AutofillTestFeature : AutofillFeature { + var topLevelFeatureEnabled: Boolean = false + var canInjectCredentials: Boolean = false + var canSaveCredentials: Boolean = false + var canGeneratePassword: Boolean = false + var canAccessCredentialManagement: Boolean = false + var onByDefault: Boolean = false + + override fun self(): Toggle = TestToggle(topLevelFeatureEnabled) + override fun canInjectCredentials(): Toggle = TestToggle(canInjectCredentials) + override fun canSaveCredentials(): Toggle = TestToggle(canSaveCredentials) + override fun canGeneratePasswords(): Toggle = TestToggle(canGeneratePassword) + override fun canAccessCredentialManagement(): Toggle = TestToggle(canAccessCredentialManagement) + override fun onByDefault(): Toggle = TestToggle(onByDefault) +} + +open class TestToggle(val enabled: Boolean) : Toggle { + override fun getRawStoredState(): Toggle.State? = null + override fun setEnabled(state: Toggle.State) {} + override fun isEnabled(): Boolean = enabled +} diff --git a/autofill/autofill-internal/src/main/AndroidManifest.xml b/autofill/autofill-internal/src/main/AndroidManifest.xml index 044d20e8df96..e419b5f250f1 100644 --- a/autofill/autofill-internal/src/main/AndroidManifest.xml +++ b/autofill/autofill-internal/src/main/AndroidManifest.xml @@ -19,7 +19,7 @@ diff --git a/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt b/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt index 8830827468c0..0e7bec1d986b 100644 --- a/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt +++ b/autofill/autofill-internal/src/main/java/com/duckduckgo/autofill/internal/AutofillInternalSettingsActivity.kt @@ -24,18 +24,27 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.autofill.api.AutofillFeature +import com.duckduckgo.autofill.api.domain.app.LoginCredentials import com.duckduckgo.autofill.api.email.EmailManager +import com.duckduckgo.autofill.api.store.AutofillStore import com.duckduckgo.autofill.impl.email.incontext.store.EmailProtectionInContextDataStore +import com.duckduckgo.autofill.impl.ui.credential.management.AutofillManagementActivity +import com.duckduckgo.autofill.internal.R.string import com.duckduckgo.autofill.internal.databinding.ActivityAutofillInternalSettingsBinding import com.duckduckgo.browser.api.UserBrowserProperties import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.view.dialog.RadioListAlertDialogBuilder +import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.ActivityScope +import java.text.SimpleDateFormat import javax.inject.Inject +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import logcat.logcat @InjectWith(ActivityScope::class) class AutofillInternalSettingsActivity : DuckDuckGoActivity() { @@ -54,6 +63,14 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() { @Inject lateinit var dispatchers: DispatcherProvider + @Inject + lateinit var autofillStore: AutofillStore + + private val dateFormatter = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.MEDIUM) + + @Inject + lateinit var autofillFeature: AutofillFeature + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -61,30 +78,141 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() { configureUiEventHandlers() refreshInstallationDaySettings() refreshDaysSinceInstall() + refreshRemoteConfigSettings() + } + + private fun refreshRemoteConfigSettings() { + lifecycleScope.launch(dispatchers.io()) { + val autofillEnabled = autofillFeature.self().isEnabled() + val onByDefault = autofillFeature.onByDefault() + val canSaveCredentials = autofillFeature.canSaveCredentials() + val canInjectCredentials = autofillFeature.canInjectCredentials() + val canGeneratePasswords = autofillFeature.canGeneratePasswords() + val canAccessCredentialManagement = autofillFeature.canAccessCredentialManagement() + + withContext(dispatchers.main()) { + binding.autofillTopLevelFeature.setSecondaryText(autofillEnabled.toString()) + binding.autofillOnByDefaultFeature.setSecondaryText("${onByDefault.isEnabled()} ${onByDefault.getRawStoredState()}") + binding.canSaveCredentialsFeature.setSecondaryText(canSaveCredentials.isEnabled().toString()) + binding.canInjectCredentialsFeature.setSecondaryText(canInjectCredentials.isEnabled().toString()) + binding.canGeneratePasswordsFeature.setSecondaryText(canGeneratePasswords.isEnabled().toString()) + binding.canAccessCredentialManagementFeature.setSecondaryText(canAccessCredentialManagement.isEnabled().toString()) + } + } } private fun configureUiEventHandlers() { + configureEmailProtectionUiEventHandlers() + configureLoginsUiEventHandlers() + } + + private fun configureLoginsUiEventHandlers() { + binding.addSampleLoginsButton.setClickListener { + val timestamp = dateFormatter.format(System.currentTimeMillis()) + lifecycleScope.launch(dispatchers.io()) { + sampleCredentials(domain = "fill.dev", username = "alice-$timestamp", password = "alice-$timestamp").save() + sampleCredentials(domain = "fill.dev", username = "bob-$timestamp", password = "bob-$timestamp").save() + sampleCredentials(domain = "subdomain1.fill.dev", username = "charlie-$timestamp", password = "charlie-$timestamp").save() + sampleCredentials(domain = "subdomain2.fill.dev", username = "daniel-$timestamp", password = "daniel-$timestamp").save() + } + } + + binding.addSampleLoginsContainingDuplicatesSameDomainButton.setClickListener { + lifecycleScope.launch(dispatchers.io()) { + repeat(3) { sampleCredentials(domain = "fill.dev", username = "user").save() } + } + } + + binding.addSampleLoginsContainingDuplicatesAcrossSubdomainsButton.setClickListener { + lifecycleScope.launch(dispatchers.io()) { + repeat(3) { sampleCredentials("https://subdomain$it.fill.dev", username = "user").save() } + } + } + + binding.clearAllSavedLoginsButton.setClickListener { + lifecycleScope.launch(dispatchers.io()) { + val count = autofillStore.getCredentialCount().first() + withContext(dispatchers.main()) { + confirmLoginDeletion(count) + } + } + } + + // keep the number of saved logins up-to-date + lifecycleScope.launch(dispatchers.main()) { + repeatOnLifecycle(Lifecycle.State.STARTED) { + autofillStore.getCredentialCount().collect { count -> + binding.clearAllSavedLoginsButton.isEnabled = count > 0 + binding.clearAllSavedLoginsButton.setSecondaryText(getString(string.autofillDevSettingsClearLoginsSubtitle, count)) + } + } + } + + binding.viewSavedLoginsButton.setClickListener { + startActivity(AutofillManagementActivity.intentDefaultList(this)) + } + } + + private fun confirmLoginDeletion(count: Int) { + TextAlertDialogBuilder(this@AutofillInternalSettingsActivity) + .setTitle(string.autofillDevSettingsClearLogins) + .setMessage(getString(string.autofillDevSettingsClearLoginsConfirmationMessage, count)) + .setDestructiveButtons(true) + .setPositiveButton(string.autofillDevSettingsClearLoginsDeleteButton) + .setNegativeButton(string.autofillDevSettingsClearLoginsCancelButton) + .addEventListener( + object : TextAlertDialogBuilder.EventListener() { + override fun onPositiveButtonClicked() { + onUserChoseToClearSavedLogins() + } + }, + ) + .show() + } + + private fun onUserChoseToClearSavedLogins() { + lifecycleScope.launch(dispatchers.io()) { + val idsToDelete = mutableListOf() + autofillStore.getAllCredentials().first().forEach { login -> + login.id?.let { + idsToDelete.add(it) + } + } + + logcat { "There are ${idsToDelete.size} logins to delete" } + + idsToDelete.forEach { + autofillStore.deleteCredentials(it) + } + + withContext(dispatchers.main()) { + Toast.makeText(this@AutofillInternalSettingsActivity, "Deleted %d logins".format(idsToDelete.size), Toast.LENGTH_SHORT).show() + } + } + } + + private fun configureEmailProtectionUiEventHandlers() { binding.emailProtectionClearNeverAskAgainButton.setClickListener { lifecycleScope.launch(dispatchers.io()) { inContextDataStore.resetNeverAskAgainChoice() - getString(R.string.autofillDevSettingsEmailProtectionNeverAskAgainChoiceCleared).showToast() + getString(string.autofillDevSettingsEmailProtectionNeverAskAgainChoiceCleared).showToast() } } binding.emailProtectionSignOutButton.setClickListener { lifecycleScope.launch(dispatchers.io()) { emailManager.signOut() - getString(R.string.autofillDevSettingsEmailProtectionSignedOut).showToast() + getString(string.autofillDevSettingsEmailProtectionSignedOut).showToast() } } @Suppress("DEPRECATION") binding.configureDaysFromInstallValue.setClickListener { RadioListAlertDialogBuilder(this) - .setTitle(R.string.autofillDevSettingsOverrideMaxInstallDialogTitle) + .setTitle(string.autofillDevSettingsOverrideMaxInstallDialogTitle) .setOptions(daysInstalledOverrideOptions().map { it.first }) - .setPositiveButton(R.string.autofillDevSettingsOverrideMaxInstallDialogOkButtonText) - .setNegativeButton(R.string.autofillDevSettingsOverrideMaxInstallDialogCancelButtonText) + .setPositiveButton(string.autofillDevSettingsOverrideMaxInstallDialogOkButtonText) + .setNegativeButton(string.autofillDevSettingsOverrideMaxInstallDialogCancelButtonText) .addEventListener( object : RadioListAlertDialogBuilder.EventListener() { override fun onPositiveButtonClicked(selectedItem: Int) { @@ -156,6 +284,20 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() { ) } + private suspend fun LoginCredentials.save() { + withContext(dispatchers.io()) { + autofillStore.saveCredentials(this@save.domain ?: "", this@save) + } + } + + private fun sampleCredentials( + domain: String = "fill.dev", + username: String, + password: String = "password-123", + ): LoginCredentials { + return LoginCredentials(username = username, password = password, domain = domain) + } + companion object { fun intent(context: Context): Intent { return Intent(context, AutofillInternalSettingsActivity::class.java) diff --git a/autofill/autofill-internal/src/main/res/layout/activity_autofill_internal_settings.xml b/autofill/autofill-internal/src/main/res/layout/activity_autofill_internal_settings.xml index 92a30b382b0f..4c0ef1757acb 100644 --- a/autofill/autofill-internal/src/main/res/layout/activity_autofill_internal_settings.xml +++ b/autofill/autofill-internal/src/main/res/layout/activity_autofill_internal_settings.xml @@ -14,52 +14,145 @@ ~ limitations under the License. --> - + android:layout_height="match_parent"> - - - + android:layout_height="match_parent" + android:orientation="vertical"> - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/autofill/autofill-internal/src/main/res/values/donottranslate.xml b/autofill/autofill-internal/src/main/res/values/donottranslate.xml index b6ddc17bf7a3..619de730289c 100644 --- a/autofill/autofill-internal/src/main/res/values/donottranslate.xml +++ b/autofill/autofill-internal/src/main/res/values/donottranslate.xml @@ -40,4 +40,19 @@ Days since app was installed: %1$d Tap to change this rule + + Logins + Delete all saved logins + Are you sure you want to delete %1$d logins? + Delete + Cancel + Number of saved logins: %1$d + View saved logins + + Add a few sample logins + Adds a few logins for https://fill.dev + Add duplicate logins (same domain) + Adds multiple logins for https://fill.dev with same username and password + Add duplicate logins (across different subdomains) + Adds multiple logins for https://fill.dev with same username and password but across different subdomains \ No newline at end of file diff --git a/autofill/autofill-store/build.gradle b/autofill/autofill-store/build.gradle index 3ca9d1e2a1b8..5ee79484a8bb 100644 --- a/autofill/autofill-store/build.gradle +++ b/autofill/autofill-store/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation project(path: ':privacy-config-api') implementation project(path: ':feature-toggles-api') implementation project(path: ':app-build-config-api') + implementation project(path: ':browser-api') implementation AndroidX.core.ktx implementation AndroidX.appCompat diff --git a/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/AutofillPrefsStore.kt b/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/AutofillPrefsStore.kt index 60097ae22ec7..b0dc265a9ea5 100644 --- a/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/AutofillPrefsStore.kt +++ b/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/AutofillPrefsStore.kt @@ -19,18 +19,27 @@ package com.duckduckgo.autofill.store import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import com.duckduckgo.autofill.api.InternalTestUserChecker +import com.duckduckgo.autofill.store.feature.AutofillDefaultStateDecider +import timber.log.Timber interface AutofillPrefsStore { var isEnabled: Boolean var autofillDeclineCount: Int var monitorDeclineCounts: Boolean var hasEverBeenPromptedToSaveLogin: Boolean + + /** + * Returns if Autofill was enabled by default. + * + * Since it's possible for the user to manually enable/disable Autofill, we need a separate mechanism to determine if it was enabled by default + * which is separate from its current state. + */ + fun wasDefaultStateEnabled(): Boolean } -class RealAutofillPrefsStore constructor( +class RealAutofillPrefsStore( private val applicationContext: Context, - private val internalTestUserChecker: InternalTestUserChecker, + private val defaultStateDecider: AutofillDefaultStateDecider, ) : AutofillPrefsStore { private val prefs: SharedPreferences by lazy { @@ -38,7 +47,22 @@ class RealAutofillPrefsStore constructor( } override var isEnabled: Boolean - get() = prefs.getBoolean(AUTOFILL_ENABLED, autofillEnabledDefaultValue()) + get(): Boolean { + // if autofill state has been manually set by user, honor that + if (autofillStateSetByUser()) { + return prefs.getBoolean(AUTOFILL_ENABLED, false) + } + + // if previously determined enabled by default was true, use that + if (wasDefaultStateEnabled()) { + return true + } + + // otherwise, determine now what the default autofill state should be + return defaultStateDecider.defaultState().also { + storeDefaultValue(it) + } + } set(value) = prefs.edit { putBoolean(AUTOFILL_ENABLED, value) } @@ -47,6 +71,33 @@ class RealAutofillPrefsStore constructor( get() = prefs.getBoolean(HAS_EVER_BEEN_PROMPTED_TO_SAVE_LOGIN, false) set(value) = prefs.edit { putBoolean(HAS_EVER_BEEN_PROMPTED_TO_SAVE_LOGIN, value) } + /** + * Returns if Autofill was enabled by default. Note, this is not necessarily the same as the current state of Autofill. + */ + override fun wasDefaultStateEnabled(): Boolean { + return prefs.getBoolean(ORIGINAL_AUTOFILL_DEFAULT_STATE_ENABLED, false) + } + + /** + * We need to later know if Autofill was enabled or disabled by default, so we store the original state here. + * + * It is safe to call this function multiple times. Internally, it will decide if it needs to store the value or not. + * + * @param enabledByDefault The value to store + */ + private fun storeDefaultValue( + enabledByDefault: Boolean, + ) { + if (enabledByDefault) { + prefs.edit { putBoolean(ORIGINAL_AUTOFILL_DEFAULT_STATE_ENABLED, true) } + Timber.i("yyy Updated default state for Autofill; originally enabled: %s", true) + } + } + + private fun autofillStateSetByUser(): Boolean { + return prefs.contains(AUTOFILL_ENABLED) + } + override var autofillDeclineCount: Int get() = prefs.getInt(AUTOFILL_DECLINE_COUNT, 0) set(value) = prefs.edit { putInt(AUTOFILL_DECLINE_COUNT, value) } @@ -55,21 +106,12 @@ class RealAutofillPrefsStore constructor( get() = prefs.getBoolean(MONITOR_AUTOFILL_DECLINES, true) set(value) = prefs.edit { putBoolean(MONITOR_AUTOFILL_DECLINES, value) } - /** - * Internal builds should have autofill enabled by default - * It'll be disabled by default for public users, even after internal testing gate removed - * If we decide to make it enabled by default for all users, we can hardcode the value to true - */ - private fun autofillEnabledDefaultValue(): Boolean { - return internalTestUserChecker.isInternalTestUser - } - companion object { const val FILENAME = "com.duckduckgo.autofill.store.autofill_store" const val AUTOFILL_ENABLED = "autofill_enabled" - const val SHOW_SAVE_LOGIN_ONBOARDING = "autofill_show_onboardind_saved_login" const val HAS_EVER_BEEN_PROMPTED_TO_SAVE_LOGIN = "autofill_has_ever_been_prompted_to_save_login" const val AUTOFILL_DECLINE_COUNT = "autofill_decline_count" const val MONITOR_AUTOFILL_DECLINES = "monitor_autofill_declines" + const val ORIGINAL_AUTOFILL_DEFAULT_STATE_ENABLED = "original_autofill_default_state_enabled" } } diff --git a/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/feature/AutofillDefaultStateDecider.kt b/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/feature/AutofillDefaultStateDecider.kt new file mode 100644 index 000000000000..9ad7f1dafb9d --- /dev/null +++ b/autofill/autofill-store/src/main/java/com/duckduckgo/autofill/store/feature/AutofillDefaultStateDecider.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 DuckDuckGo + * + * 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 + * + * http://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. + */ + +package com.duckduckgo.autofill.store.feature + +import com.duckduckgo.autofill.api.AutofillFeature +import com.duckduckgo.autofill.api.InternalTestUserChecker +import com.duckduckgo.browser.api.UserBrowserProperties +import timber.log.Timber + +interface AutofillDefaultStateDecider { + fun defaultState(): Boolean +} + +class RealAutofillDefaultStateDecider( + private val userBrowserProperties: UserBrowserProperties, + private val autofillFeature: AutofillFeature, + private val internalTestUserChecker: InternalTestUserChecker, +) : AutofillDefaultStateDecider { + + override fun defaultState(): Boolean { + if (internalTestUserChecker.isInternalTestUser) { + Timber.v("Internal testing user, enabling autofill by default") + return true + } + + if (!autofillFeature.onByDefault().isEnabled()) { + return false + } + + if (userBrowserProperties.daysSinceInstalled() > 0L) { + return false + } + + Timber.i("Determined Autofill should be enabled by default") + return true + } +}