From da4c7e7ed675c3bf405668739c3012d140856109 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:42 +0200 Subject: Adding upstream version 126.0. Signed-off-by: Daniel Baumann --- .../components/feature/autofill/README.md | 19 ++ .../components/feature/autofill/build.gradle | 60 ++++++ .../components/feature/autofill/proguard-rules.pro | 21 ++ .../feature/autofill/src/main/AndroidManifest.xml | 11 + .../feature/autofill/AbstractAutofillService.kt | 83 ++++++++ .../feature/autofill/AutofillConfiguration.kt | 45 ++++ .../feature/autofill/AutofillUseCases.kt | 83 ++++++++ .../autofill/authenticator/Authenticator.kt | 61 ++++++ .../authenticator/BiometricAuthenticator.kt | 81 +++++++ .../authenticator/DeviceCredentialAuthenticator.kt | 51 +++++ .../feature/autofill/facts/AutofillFacts.kt | 106 ++++++++++ .../feature/autofill/handler/FillRequestHandler.kt | 106 ++++++++++ .../feature/autofill/lock/AutofillLock.kt | 48 +++++ .../autofill/preference/AutofillPreference.kt | 59 ++++++ .../autofill/response/dataset/DatasetBuilder.kt | 18 ++ .../response/dataset/LoginDatasetBuilder.kt | 206 ++++++++++++++++++ .../response/dataset/SearchDatasetBuilder.kt | 76 +++++++ .../response/fill/AuthFillResponseBuilder.kt | 125 +++++++++++ .../autofill/response/fill/FillResponseBuilder.kt | 18 ++ .../response/fill/LoginFillResponseBuilder.kt | 60 ++++++ .../feature/autofill/structure/ParsedStructure.kt | 110 ++++++++++ .../autofill/structure/ParsedStructureBuilder.kt | 229 ++++++++++++++++++++ .../feature/autofill/structure/RawStructure.kt | 36 ++++ .../autofill/structure/ViewNodeNavigator.kt | 186 ++++++++++++++++ .../autofill/ui/AbstractAutofillConfirmActivity.kt | 139 ++++++++++++ .../autofill/ui/AbstractAutofillSearchActivity.kt | 144 +++++++++++++ .../autofill/ui/AbstractAutofillUnlockActivity.kt | 125 +++++++++++ .../feature/autofill/ui/search/LoginViewHolder.kt | 30 +++ .../feature/autofill/ui/search/LoginsAdapter.kt | 47 +++++ .../autofill/verify/CredentialAccessVerifier.kt | 50 +++++ .../res/layout/mozac_feature_autofill_login.xml | 23 ++ .../layout/mozac_feature_autofill_preference.xml | 11 + .../res/layout/mozac_feature_autofill_search.xml | 24 +++ .../autofill/src/main/res/values-am/strings.xml | 45 ++++ .../autofill/src/main/res/values-ar/strings.xml | 46 ++++ .../autofill/src/main/res/values-ast/strings.xml | 45 ++++ .../autofill/src/main/res/values-azb/strings.xml | 45 ++++ .../autofill/src/main/res/values-be/strings.xml | 45 ++++ .../autofill/src/main/res/values-bg/strings.xml | 46 ++++ .../autofill/src/main/res/values-br/strings.xml | 45 ++++ .../autofill/src/main/res/values-bs/strings.xml | 45 ++++ .../autofill/src/main/res/values-ca/strings.xml | 45 ++++ .../autofill/src/main/res/values-cak/strings.xml | 45 ++++ .../autofill/src/main/res/values-ceb/strings.xml | 45 ++++ .../autofill/src/main/res/values-ckb/strings.xml | 45 ++++ .../autofill/src/main/res/values-co/strings.xml | 45 ++++ .../autofill/src/main/res/values-cs/strings.xml | 45 ++++ .../autofill/src/main/res/values-cy/strings.xml | 45 ++++ .../autofill/src/main/res/values-da/strings.xml | 45 ++++ .../autofill/src/main/res/values-de/strings.xml | 45 ++++ .../autofill/src/main/res/values-dsb/strings.xml | 45 ++++ .../autofill/src/main/res/values-el/strings.xml | 45 ++++ .../src/main/res/values-en-rCA/strings.xml | 45 ++++ .../src/main/res/values-en-rGB/strings.xml | 45 ++++ .../autofill/src/main/res/values-eo/strings.xml | 46 ++++ .../src/main/res/values-es-rAR/strings.xml | 45 ++++ .../src/main/res/values-es-rCL/strings.xml | 45 ++++ .../src/main/res/values-es-rES/strings.xml | 45 ++++ .../src/main/res/values-es-rMX/strings.xml | 45 ++++ .../autofill/src/main/res/values-es/strings.xml | 45 ++++ .../autofill/src/main/res/values-et/strings.xml | 45 ++++ .../autofill/src/main/res/values-eu/strings.xml | 45 ++++ .../autofill/src/main/res/values-fa/strings.xml | 45 ++++ .../autofill/src/main/res/values-ff/strings.xml | 17 ++ .../autofill/src/main/res/values-fi/strings.xml | 45 ++++ .../autofill/src/main/res/values-fr/strings.xml | 45 ++++ .../autofill/src/main/res/values-fur/strings.xml | 45 ++++ .../src/main/res/values-fy-rNL/strings.xml | 45 ++++ .../autofill/src/main/res/values-gd/strings.xml | 45 ++++ .../autofill/src/main/res/values-gl/strings.xml | 45 ++++ .../autofill/src/main/res/values-gn/strings.xml | 45 ++++ .../src/main/res/values-hi-rIN/strings.xml | 38 ++++ .../autofill/src/main/res/values-hil/strings.xml | 17 ++ .../autofill/src/main/res/values-hr/strings.xml | 45 ++++ .../autofill/src/main/res/values-hsb/strings.xml | 45 ++++ .../autofill/src/main/res/values-hu/strings.xml | 45 ++++ .../src/main/res/values-hy-rAM/strings.xml | 45 ++++ .../autofill/src/main/res/values-ia/strings.xml | 45 ++++ .../autofill/src/main/res/values-in/strings.xml | 45 ++++ .../autofill/src/main/res/values-is/strings.xml | 45 ++++ .../autofill/src/main/res/values-it/strings.xml | 45 ++++ .../autofill/src/main/res/values-iw/strings.xml | 45 ++++ .../autofill/src/main/res/values-ja/strings.xml | 45 ++++ .../autofill/src/main/res/values-ka/strings.xml | 45 ++++ .../autofill/src/main/res/values-kaa/strings.xml | 46 ++++ .../autofill/src/main/res/values-kab/strings.xml | 45 ++++ .../autofill/src/main/res/values-kk/strings.xml | 45 ++++ .../autofill/src/main/res/values-kmr/strings.xml | 45 ++++ .../autofill/src/main/res/values-ko/strings.xml | 45 ++++ .../autofill/src/main/res/values-lo/strings.xml | 45 ++++ .../autofill/src/main/res/values-lt/strings.xml | 45 ++++ .../autofill/src/main/res/values-mix/strings.xml | 21 ++ .../autofill/src/main/res/values-my/strings.xml | 45 ++++ .../src/main/res/values-nb-rNO/strings.xml | 45 ++++ .../src/main/res/values-ne-rNP/strings.xml | 45 ++++ .../autofill/src/main/res/values-nl/strings.xml | 45 ++++ .../src/main/res/values-nn-rNO/strings.xml | 45 ++++ .../autofill/src/main/res/values-oc/strings.xml | 45 ++++ .../autofill/src/main/res/values-or/strings.xml | 25 +++ .../src/main/res/values-pa-rIN/strings.xml | 45 ++++ .../src/main/res/values-pa-rPK/strings.xml | 46 ++++ .../autofill/src/main/res/values-pl/strings.xml | 45 ++++ .../src/main/res/values-pt-rBR/strings.xml | 45 ++++ .../src/main/res/values-pt-rPT/strings.xml | 45 ++++ .../autofill/src/main/res/values-rm/strings.xml | 45 ++++ .../autofill/src/main/res/values-ro/strings.xml | 14 ++ .../autofill/src/main/res/values-ru/strings.xml | 45 ++++ .../autofill/src/main/res/values-sat/strings.xml | 45 ++++ .../autofill/src/main/res/values-sc/strings.xml | 37 ++++ .../autofill/src/main/res/values-si/strings.xml | 46 ++++ .../autofill/src/main/res/values-sk/strings.xml | 45 ++++ .../autofill/src/main/res/values-skr/strings.xml | 46 ++++ .../autofill/src/main/res/values-sl/strings.xml | 45 ++++ .../autofill/src/main/res/values-sq/strings.xml | 45 ++++ .../autofill/src/main/res/values-sr/strings.xml | 45 ++++ .../autofill/src/main/res/values-su/strings.xml | 45 ++++ .../src/main/res/values-sv-rSE/strings.xml | 45 ++++ .../autofill/src/main/res/values-ta/strings.xml | 41 ++++ .../autofill/src/main/res/values-te/strings.xml | 25 +++ .../autofill/src/main/res/values-tg/strings.xml | 45 ++++ .../autofill/src/main/res/values-th/strings.xml | 45 ++++ .../autofill/src/main/res/values-tl/strings.xml | 45 ++++ .../autofill/src/main/res/values-tok/strings.xml | 45 ++++ .../autofill/src/main/res/values-tr/strings.xml | 45 ++++ .../autofill/src/main/res/values-trs/strings.xml | 45 ++++ .../autofill/src/main/res/values-tt/strings.xml | 37 ++++ .../autofill/src/main/res/values-tzm/strings.xml | 34 +++ .../autofill/src/main/res/values-ug/strings.xml | 46 ++++ .../autofill/src/main/res/values-uk/strings.xml | 45 ++++ .../autofill/src/main/res/values-ur/strings.xml | 37 ++++ .../autofill/src/main/res/values-uz/strings.xml | 45 ++++ .../autofill/src/main/res/values-vec/strings.xml | 45 ++++ .../autofill/src/main/res/values-vi/strings.xml | 45 ++++ .../autofill/src/main/res/values-yo/strings.xml | 46 ++++ .../src/main/res/values-zh-rCN/strings.xml | 45 ++++ .../src/main/res/values-zh-rTW/strings.xml | 45 ++++ .../autofill/src/main/res/values/strings.xml | 48 +++++ .../feature/autofill/AutofillUseCasesTest.kt | 192 +++++++++++++++++ .../autofill/handler/FillRequestHandlerTest.kt | 235 +++++++++++++++++++++ .../autofill/structure/ParsedStructureTest.kt | 52 +++++ .../feature/autofill/test/DOMNavigator.kt | 133 ++++++++++++ .../feature/autofill/test/MockStructure.kt | 32 +++ .../src/test/resources/fixtures/app_expensify.xml | 46 ++++ .../src/test/resources/fixtures/app_facebook.xml | 14 ++ .../test/resources/fixtures/app_facebook_lite.xml | 59 ++++++ .../test/resources/fixtures/app_messenger_lite.xml | 29 +++ .../src/test/resources/fixtures/app_twitter.xml | 51 +++++ .../fixtures/browser_fenix_amazon.co.uk.xml | 68 ++++++ .../resources/fixtures/browser_webview_gmail.xml | 15 ++ .../org.mockito.plugins.MockMaker | 2 + .../src/test/resources/robolectric.properties | 1 + 151 files changed, 7915 insertions(+) create mode 100644 mobile/android/android-components/components/feature/autofill/README.md create mode 100644 mobile/android/android-components/components/feature/autofill/build.gradle create mode 100644 mobile/android/android-components/components/feature/autofill/proguard-rules.pro create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/AndroidManifest.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AbstractAutofillService.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillConfiguration.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillUseCases.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/Authenticator.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/BiometricAuthenticator.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/DeviceCredentialAuthenticator.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/facts/AutofillFacts.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/handler/FillRequestHandler.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/lock/AutofillLock.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/preference/AutofillPreference.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/DatasetBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/LoginDatasetBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/SearchDatasetBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/FillResponseBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/LoginFillResponseBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructureBuilder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/RawStructure.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ViewNodeNavigator.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillConfirmActivity.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillSearchActivity.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginViewHolder.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginsAdapter.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/verify/CredentialAccessVerifier.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_login.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_preference.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_search.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-am/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ar/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ast/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-azb/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-be/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-bg/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-br/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-bs/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ca/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-cak/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ceb/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ckb/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-co/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-cs/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-cy/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-da/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-de/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-dsb/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-el/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rCA/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rGB/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-eo/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rAR/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rCL/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rES/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rMX/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-es/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-et/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-eu/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-fa/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ff/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-fi/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-fr/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-fur/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-fy-rNL/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-gd/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-gl/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-gn/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-hi-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-hil/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-hr/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-hsb/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-hu/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-hy-rAM/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ia/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-in/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-is/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-it/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-iw/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ja/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ka/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-kaa/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-kab/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-kk/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-kmr/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ko/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-lo/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-lt/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-mix/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-my/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-nb-rNO/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ne-rNP/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-nl/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-nn-rNO/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-oc/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-or/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rIN/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rPK/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-pl/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rBR/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rPT/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-rm/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ro/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ru/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sat/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sc/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-si/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sk/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-skr/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sl/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sq/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sr/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-su/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-sv-rSE/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ta/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-te/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-tg/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-th/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-tl/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-tok/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-tr/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-trs/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-tt/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-tzm/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ug/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-uk/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-ur/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-uz/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-vec/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-vi/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-yo/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rCN/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rTW/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/main/res/values/strings.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/AutofillUseCasesTest.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/DOMNavigator.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_expensify.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook_lite.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_messenger_lite.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_twitter.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/browser_fenix_amazon.co.uk.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/browser_webview_gmail.xml create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 mobile/android/android-components/components/feature/autofill/src/test/resources/robolectric.properties (limited to 'mobile/android/android-components/components/feature/autofill') diff --git a/mobile/android/android-components/components/feature/autofill/README.md b/mobile/android/android-components/components/feature/autofill/README.md new file mode 100644 index 0000000000..281bd140f2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/README.md @@ -0,0 +1,19 @@ +# [Android Components](../../../README.md) > Feature > Autofill + +A component that provides support for Android's Autofill framework. + +## Usage + +### Setting up the dependency + +Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): + +```Groovy +implementation "org.mozilla.components:feature-autofill:{latest-version}" +``` + +## License + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/mobile/android/android-components/components/feature/autofill/build.gradle b/mobile/android/android-components/components/feature/autofill/build.gradle new file mode 100644 index 0000000000..033eebdfe1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/build.gradle @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' + +android { + defaultConfig { + minSdkVersion config.minSdkVersion + compileSdk config.compileSdkVersion + targetSdkVersion config.targetSdkVersion + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + namespace 'mozilla.components.feature.autofill' +} + +dependencies { + implementation platform(ComponentsDependencies.androidx_compose_bom) + implementation project(':concept-fetch') + implementation project(':concept-storage') + implementation project(':lib-publicsuffixlist') + implementation project(':service-digitalassetlinks') + implementation project(':support-base') + implementation project(':support-ktx') + implementation project(":support-utils") + implementation project(':ui-widgets') + + implementation ComponentsDependencies.androidx_annotation + implementation ComponentsDependencies.androidx_autofill + implementation ComponentsDependencies.androidx_biometric + implementation ComponentsDependencies.androidx_fragment + implementation ComponentsDependencies.androidx_lifecycle_runtime + implementation ComponentsDependencies.androidx_recyclerview + implementation ComponentsDependencies.androidx_core_ktx + implementation ComponentsDependencies.androidx_preferences + + implementation ComponentsDependencies.kotlin_coroutines + + testImplementation project(':support-test') + testImplementation project(':lib-fetch-okhttp') + + testImplementation ComponentsDependencies.androidx_test_core + testImplementation ComponentsDependencies.androidx_test_junit + testImplementation ComponentsDependencies.testing_robolectric + testImplementation ComponentsDependencies.testing_coroutines + testImplementation ComponentsDependencies.testing_mockwebserver +} + +apply from: '../../../android-lint.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/mobile/android/android-components/components/feature/autofill/proguard-rules.pro b/mobile/android/android-components/components/feature/autofill/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mobile/android/android-components/components/feature/autofill/src/main/AndroidManifest.xml b/mobile/android/android-components/components/feature/autofill/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..80ef4db858 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AbstractAutofillService.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AbstractAutofillService.kt new file mode 100644 index 0000000000..1d0fb7f97e --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AbstractAutofillService.kt @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill + +import android.os.Build +import android.os.CancellationSignal +import android.service.autofill.AutofillService +import android.service.autofill.FillCallback +import android.service.autofill.FillRequest +import android.service.autofill.SaveCallback +import android.service.autofill.SaveRequest +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import mozilla.components.feature.autofill.handler.FillRequestHandler +import mozilla.components.feature.autofill.handler.MAX_LOGINS +import mozilla.components.feature.autofill.structure.toRawStructure + +/** + * Service responsible for implementing Android's Autofill framework. + */ +@RequiresApi(Build.VERSION_CODES.O) +abstract class AbstractAutofillService : AutofillService() { + abstract val configuration: AutofillConfiguration + + private val fillHandler by lazy { FillRequestHandler(context = this, configuration) } + + override fun onFillRequest( + request: FillRequest, + cancellationSignal: CancellationSignal, + callback: FillCallback, + ) { + // We are using GlobalScope here instead of a scope bound to the service since the service + // seems to get destroyed before we invoke a method on the callback. So we need a scope that + // lives longer than the service. + @OptIn(DelicateCoroutinesApi::class) + GlobalScope.launch(Dispatchers.IO) { + // You may be wondering why we translate the AssistStructure into a RawStructure and then + // create a FillResponseBuilder that outputs the FillResponse. This is purely for testing. + // Neither AssistStructure nor FillResponse can be created by us and they do not let us + // inspect their data. So we create these intermediate objects that we can create and + // inspect in unit tests. + val structure = request.fillContexts.last().structure.toRawStructure() + val responseBuilder = fillHandler.handle( + structure, + maxSuggestionCount = request.getMaxSuggestionCount(), + ) + val response = responseBuilder?.build( + this@AbstractAutofillService, + configuration, + request.getInlinePresentationSpec(), + ) + callback.onSuccess(response) + } + } + + override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { + // This callback should not get invoked since we do not indicate that we are interested in + // saving any data (yet). If for whatever reason it does get invoked then we pretent that + // we handled the request successfully. Calling onFailure() requires to pass in a message + // and on Android systems before Q this message may be shown in a toast. + callback.onSuccess() + } +} + +internal fun FillRequest.getInlinePresentationSpec(): InlinePresentationSpec? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return inlineSuggestionsRequest?.inlinePresentationSpecs?.last() + } else { + return null + } +} + +internal fun FillRequest.getMaxSuggestionCount() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + (inlineSuggestionsRequest?.maxSuggestionCount ?: 1) - 1 // space for search chip +} else { + MAX_LOGINS +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillConfiguration.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillConfiguration.kt new file mode 100644 index 0000000000..79b31d81f5 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillConfiguration.kt @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill + +import mozilla.components.concept.fetch.Client +import mozilla.components.concept.storage.LoginsStorage +import mozilla.components.feature.autofill.lock.AutofillLock +import mozilla.components.feature.autofill.ui.AbstractAutofillConfirmActivity +import mozilla.components.feature.autofill.ui.AbstractAutofillSearchActivity +import mozilla.components.feature.autofill.ui.AbstractAutofillUnlockActivity +import mozilla.components.feature.autofill.verify.CredentialAccessVerifier +import mozilla.components.lib.publicsuffixlist.PublicSuffixList +import mozilla.components.service.digitalassetlinks.local.StatementApi +import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker + +/** + * Configuration for the "Autofill" feature. + * + * @property storage The [LoginsStorage] used for looking up accounts and passwords to autofill. + * @property publicSuffixList Global instance of the public suffix list used for matching domains. + * @property unlockActivity Activity class that implements [AbstractAutofillUnlockActivity]. + * @property confirmActivity Activity class that implements [AbstractAutofillConfirmActivity]. + * @property searchActivity Activity class that implements [AbstractAutofillSearchActivity]. + * @property applicationName The name of the application that integrates this feature. Used in UI. + * @property lock Global [AutofillLock] instance used for unlocking the autofill service. + * @property verifier Helper for verifying the connection between a domain and an application. + * @property activityRequestCode The request code used for pending intents that launch an activity + * on behalf of the autofill service. + */ +data class AutofillConfiguration( + val storage: LoginsStorage, + val publicSuffixList: PublicSuffixList, + val unlockActivity: Class<*>, + val confirmActivity: Class<*>, + val searchActivity: Class<*>, + val applicationName: String, + val httpClient: Client, + val lock: AutofillLock = AutofillLock(), + val verifier: CredentialAccessVerifier = CredentialAccessVerifier( + StatementRelationChecker(StatementApi(httpClient)), + ), + val activityRequestCode: Int = 1010, +) diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillUseCases.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillUseCases.kt new file mode 100644 index 0000000000..8a4a882caf --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/AutofillUseCases.kt @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.view.autofill.AutofillManager +import androidx.annotation.VisibleForTesting +import mozilla.components.support.base.log.logger.Logger + +/** + * Use cases for common Android Autofill tasks. + */ +@SuppressLint("NewApi") // All API calls are checked properly. +class AutofillUseCases( + @VisibleForTesting sdkVersion: Int = Build.VERSION.SDK_INT, +) { + private val isAutofillAvailable = sdkVersion >= Build.VERSION_CODES.O + private val logger = Logger("AutofillUseCases") + + /** + * Returns true if Autofill is supported by the current device. + */ + fun isSupported(context: Context): Boolean { + if (!isAutofillAvailable) { + return false + } + + return context.getSystemService(AutofillManager::class.java) + .isAutofillSupported + } + + /** + * Returns true if this application is providing Autofill services for the current user. + */ + @Suppress("TooGenericExceptionCaught") + fun isEnabled(context: Context): Boolean { + if (!isAutofillAvailable) { + return false + } + + return try { + context.getSystemService(AutofillManager::class.java) + .hasEnabledAutofillServices() + } catch (e: RuntimeException) { + // Without more detail about why the system service has timed out, it's easiest to assume + // that the failure will continue and so disable the service for now. + logger.error("System service lookup has timed out") + false + } + } + + /** + * Opens the system's autofill settings to let the user select an autofill service. + */ + fun enable(context: Context) { + if (!isAutofillAvailable) { + return + } + + val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE) + intent.data = Uri.parse("package:${context.packageName}") + context.startActivity(intent) + } + + /** + * Disables autofill if this application is providing Autofill services for the current user. + */ + fun disable(context: Context) { + if (!isAutofillAvailable) { + return + } + + context.getSystemService(AutofillManager::class.java) + .disableAutofillServices() + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/Authenticator.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/Authenticator.kt new file mode 100644 index 0000000000..e1034c634e --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/Authenticator.kt @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.authenticator + +import android.content.Context +import androidx.fragment.app.FragmentActivity +import mozilla.components.feature.autofill.AutofillConfiguration + +/** + * Shared interface to support multiple authentication methods. + */ +internal interface Authenticator { + /** + * Shows an authentication prompt and will invoke [callback] once authentication succeeded or + * failed. + */ + fun prompt(activity: FragmentActivity, callback: Callback) + + /** + * For passing an activity launch result to the authenticator. + */ + fun onActivityResult(requestCode: Int, resultCode: Int) + + /** + * Callback getting invoked by an [Authenticator] implementation once authentication completed. + */ + interface Callback { + /** + * Called when a biometric (e.g. fingerprint, face, etc.) is recognized, indicating that the + * user has successfully authenticated. + */ + fun onAuthenticationSucceeded() + + /** + * Called when a biometric (e.g. fingerprint, face, etc.) is presented but not recognized as + * belonging to the user. + */ + fun onAuthenticationFailed() + + /** + * Called when an unrecoverable error has been encountered and authentication has stopped. + */ + fun onAuthenticationError() + } +} + +/** + * Creates an [Authenticator] for the current device setup. + */ +internal fun createAuthenticator( + context: Context, + configuration: AutofillConfiguration, +): Authenticator? { + return when { + BiometricAuthenticator.isAvailable(context) -> BiometricAuthenticator(configuration) + DeviceCredentialAuthenticator.isAvailable(context) -> DeviceCredentialAuthenticator(configuration) + else -> null + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/BiometricAuthenticator.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/BiometricAuthenticator.kt new file mode 100644 index 0000000000..8c82fa3452 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/BiometricAuthenticator.kt @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.authenticator + +import android.content.Context +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.R + +private const val AUTHENTICATORS = + BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL + +/** + * [Authenticator] implementation that uses [BiometricManager] and [BiometricPrompt] to authorize + * the user. + */ +internal class BiometricAuthenticator( + private val configuration: AutofillConfiguration, +) : Authenticator { + + override fun prompt(activity: FragmentActivity, callback: Authenticator.Callback) { + val executor = ContextCompat.getMainExecutor(activity) + val biometricPrompt = BiometricPrompt(activity, executor, PromptCallback(callback)) + + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setAllowedAuthenticators(AUTHENTICATORS) + .setTitle( + activity.getString( + R.string.mozac_feature_autofill_popup_unlock_application, + configuration.applicationName, + ), + ) + .build() + + biometricPrompt.authenticate(promptInfo) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int) = Unit + + companion object { + /** + * Returns `true` if biometric authentication with [BiometricAuthenticator] is possible. + */ + fun isAvailable(context: Context): Boolean { + val biometricManager = BiometricManager.from(context) + return biometricManager.canAuthenticate(AUTHENTICATORS) == + BiometricManager.BIOMETRIC_SUCCESS + } + + /** + * Returns `true` if biometric authentication with [BiometricAuthenticator] is not possible + * yet, but the user can enroll and create credentials for it. + */ + fun canEnroll(context: Context): Boolean { + val biometricManager = BiometricManager.from(context) + return biometricManager.canAuthenticate(AUTHENTICATORS) == + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED + } + } +} + +private class PromptCallback( + private val callback: Authenticator.Callback, +) : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + callback.onAuthenticationSucceeded() + } + + override fun onAuthenticationFailed() { + callback.onAuthenticationFailed() + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + callback.onAuthenticationError() + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/DeviceCredentialAuthenticator.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/DeviceCredentialAuthenticator.kt new file mode 100644 index 0000000000..c8780d9d10 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/authenticator/DeviceCredentialAuthenticator.kt @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.authenticator + +import android.app.Activity.RESULT_OK +import android.app.KeyguardManager +import android.content.Context +import androidx.fragment.app.FragmentActivity +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.R + +/** + * [Authenticator] implementation that uses Android's [KeyguardManager] to authenticate the user. + */ +internal class DeviceCredentialAuthenticator( + private val configuration: AutofillConfiguration, +) : Authenticator { + private var callback: Authenticator.Callback? = null + + @Suppress("Deprecation") // This is only used when BiometricPrompt is unavailable + override fun prompt(activity: FragmentActivity, callback: Authenticator.Callback) { + this.callback = callback + + val manager = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager? + val intent = manager!!.createConfirmDeviceCredentialIntent( + activity.getString( + R.string.mozac_feature_autofill_popup_unlock_application, + configuration.applicationName, + ), + "", + ) + activity.startActivityForResult(intent, configuration.activityRequestCode) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int) { + if (requestCode == configuration.activityRequestCode && resultCode == RESULT_OK) { + callback?.onAuthenticationSucceeded() + } else { + callback?.onAuthenticationFailed() + } + } + + companion object { + fun isAvailable(context: Context): Boolean { + val manager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager? + return manager?.isKeyguardSecure == true + } + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/facts/AutofillFacts.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/facts/AutofillFacts.kt new file mode 100644 index 0000000000..51a82dea07 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/facts/AutofillFacts.kt @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.facts + +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.Fact +import mozilla.components.support.base.facts.collect + +/** + * [Fact]s emitted by the `feature-autofill` component. + */ +class AutofillFacts { + /** + * Items the `feature-autofill` component emits [Fact]s for. + */ + object Items { + const val AUTOFILL_REQUEST = "autofill_request" + const val AUTOFILL_CONFIRMATION = "autofill_confirmation" + const val AUTOFILL_SEARCH = "autofill_search" + const val AUTOFILL_LOCK = "autofill_lock" + const val AUTOFILL_LOGIN_PASSWORD_DETECTED = "autofill_login_password_detected" + } + + /** + * Metadata keys used by some [Fact]s emitted by the `feature-autofill` component. + */ + object Metadata { + const val HAS_MATCHING_LOGINS = "has_matching_logins" + const val NEEDS_CONFIRMATION = "needs_confirmation" + } +} + +internal fun emitLoginPasswordDetectedFact() { + Fact( + Component.FEATURE_AUTOFILL, + Action.INTERACTION, + AutofillFacts.Items.AUTOFILL_LOGIN_PASSWORD_DETECTED, + metadata = null, + ).collect() +} + +internal fun emitAutofillRequestFact( + hasLogins: Boolean, + needsConfirmation: Boolean? = null, +) { + Fact( + Component.FEATURE_AUTOFILL, + Action.SYSTEM, + AutofillFacts.Items.AUTOFILL_REQUEST, + metadata = requestMetadata(hasLogins, needsConfirmation), + ).collect() +} + +internal fun emitAutofillConfirmationFact( + confirmed: Boolean, +) { + Fact( + Component.FEATURE_AUTOFILL, + if (confirmed) { Action.CONFIRM } else { Action.CANCEL }, + AutofillFacts.Items.AUTOFILL_CONFIRMATION, + ).collect() +} + +internal fun emitAutofillSearchDisplayedFact() { + Fact( + Component.FEATURE_AUTOFILL, + Action.DISPLAY, + AutofillFacts.Items.AUTOFILL_SEARCH, + ).collect() +} + +internal fun emitAutofillSearchSelectedFact() { + Fact( + Component.FEATURE_AUTOFILL, + Action.SELECT, + AutofillFacts.Items.AUTOFILL_SEARCH, + ).collect() +} + +internal fun emitAutofillLock( + unlocked: Boolean, +) { + Fact( + Component.FEATURE_AUTOFILL, + if (unlocked) { Action.CONFIRM } else { Action.CANCEL }, + AutofillFacts.Items.AUTOFILL_LOCK, + ).collect() +} + +private fun requestMetadata( + hasLogins: Boolean, + needsConfirmation: Boolean? = null, +): Map { + val metadata = mutableMapOf( + AutofillFacts.Metadata.HAS_MATCHING_LOGINS to hasLogins, + ) + + needsConfirmation?.let { + metadata[AutofillFacts.Metadata.NEEDS_CONFIRMATION] = needsConfirmation + } + + return metadata +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/handler/FillRequestHandler.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/handler/FillRequestHandler.kt new file mode 100644 index 0000000000..154629f320 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/handler/FillRequestHandler.kt @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.handler + +import android.annotation.SuppressLint +import android.app.assist.AssistStructure +import android.content.Context +import android.os.Build +import android.service.autofill.FillRequest +import android.service.autofill.FillResponse +import androidx.annotation.RequiresApi +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.facts.emitAutofillRequestFact +import mozilla.components.feature.autofill.response.dataset.DatasetBuilder +import mozilla.components.feature.autofill.response.dataset.LoginDatasetBuilder +import mozilla.components.feature.autofill.response.fill.AuthFillResponseBuilder +import mozilla.components.feature.autofill.response.fill.FillResponseBuilder +import mozilla.components.feature.autofill.response.fill.LoginFillResponseBuilder +import mozilla.components.feature.autofill.structure.ParsedStructure +import mozilla.components.feature.autofill.structure.RawStructure +import mozilla.components.feature.autofill.structure.getLookupDomain +import mozilla.components.feature.autofill.structure.parseStructure +import kotlin.math.min + +internal const val EXTRA_LOGIN_ID = "loginId" + +// Maximum number of logins we are going to display in the autofill overlay. +internal const val MAX_LOGINS = 10 + +/** + * Class responsible for handling [FillRequest]s and returning [FillResponse]s. + */ +@RequiresApi(Build.VERSION_CODES.O) +internal class FillRequestHandler( + private val context: Context, + private val configuration: AutofillConfiguration, +) { + /** + * Handles a fill request for the given [AssistStructure] and returns a matching [FillResponse] + * or `null` if the request could not be handled or the passed in [AssistStructure] is `null`. + */ + @SuppressLint("InlinedApi") + @Suppress("ReturnCount") + suspend fun handle( + structure: RawStructure?, + forceUnlock: Boolean = false, + maxSuggestionCount: Int = MAX_LOGINS, + ): FillResponseBuilder? { + if (structure == null) { + return null + } + + val parsedStructure = parseStructure(context, structure) ?: return null + return handle(parsedStructure, forceUnlock, maxSuggestionCount) + } + + suspend fun handle( + parsedStructure: ParsedStructure, + forceUnlock: Boolean = false, + maxSuggestionCount: Int = MAX_LOGINS, + ): FillResponseBuilder { + val lookupDomain = parsedStructure.getLookupDomain(configuration.publicSuffixList) + val needsConfirmation = !configuration.verifier.hasCredentialRelationship( + context, + lookupDomain, + parsedStructure.packageName, + ) + + val logins = configuration.storage + .getByBaseDomain(lookupDomain) + .take(min(MAX_LOGINS, maxSuggestionCount)) + + return if (!configuration.lock.keepUnlocked() && !forceUnlock) { + AuthFillResponseBuilder(parsedStructure, maxSuggestionCount) + } else { + emitAutofillRequestFact(hasLogins = logins.isNotEmpty(), needsConfirmation) + LoginFillResponseBuilder(parsedStructure, logins, needsConfirmation) + } + } + + /** + * Handles a fill request for the given [RawStructure] and returns only a [DatasetBuilder] for + * the given [loginId] - or `null` if the request could not be handled or the passed in + * [RawStructure] is `null` + */ + @Suppress("ReturnCount") + suspend fun handleConfirmation(structure: RawStructure?, loginId: String): DatasetBuilder? { + if (structure == null) { + return null + } + + val parsedStructure = parseStructure(context, structure) ?: return null + val lookupDomain = parsedStructure.getLookupDomain(configuration.publicSuffixList) + + val logins = configuration.storage.getByBaseDomain(lookupDomain) + if (logins.isEmpty()) { + return null + } + + val login = logins.firstOrNull { login -> login.guid == loginId } ?: return null + + return LoginDatasetBuilder(parsedStructure, login, needsConfirmation = false) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/lock/AutofillLock.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/lock/AutofillLock.kt new file mode 100644 index 0000000000..406dee8abf --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/lock/AutofillLock.kt @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.lock + +import mozilla.components.concept.storage.LoginsStorage +import mozilla.components.support.base.android.Clock + +// Time after the last unlock that will require a new unlock +private const val AUTOLOCK_TIME = 5 * 60 * 1000 + +/** + * Helper for keeping track of the lock/unlock state for autofill. The actual unlocking or + * decrypting of the underlying storage is done by the [LoginsStorage] implementation. + */ +class AutofillLock { + private var lastUnlockTimestmap: Long = 0 + + /** + * Checks whether the autofill lock is still unlocked and whether autofill options will be shown + * without authenticating again. + */ + @Synchronized + fun isUnlocked() = lastUnlockTimestmap + AUTOLOCK_TIME >= Clock.elapsedRealtime() + + /** + * If the autofill lock is unlocked then this will keep it unlocked by extending the time until + * it will automatically get locked again. + */ + @Synchronized + fun keepUnlocked(): Boolean { + return if (isUnlocked()) { + unlock() + true + } else { + false + } + } + + /** + * Unlocks the autofill lock. + */ + @Synchronized + fun unlock() { + lastUnlockTimestmap = Clock.elapsedRealtime() + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/preference/AutofillPreference.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/preference/AutofillPreference.kt new file mode 100644 index 0000000000..0d5d6913c6 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/preference/AutofillPreference.kt @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.preference + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.SwitchCompat +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import mozilla.components.feature.autofill.AutofillUseCases +import mozilla.components.feature.autofill.R + +/** + * Preference showing a switch to enable this app as the preferred autofill service of the user. + * + * When getting enabled this preference will launch Android's system setting for selecting an + * autofill service. + */ +class AutofillPreference( + context: Context, + attrs: AttributeSet? = null, +) : Preference(context, attrs) { + private val useCases = AutofillUseCases() + private var switchView: SwitchCompat? = null + + init { + widgetLayoutResource = R.layout.mozac_feature_autofill_preference + isVisible = useCases.isSupported(context) + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + switchView = holder.findViewById(R.id.switch_widget) as SwitchCompat + + update() + } + + override fun onClick() { + super.onClick() + + if (switchView?.isChecked == true) { + useCases.disable(context) + switchView?.isChecked = false + } else { + useCases.enable(context) + } + } + + /** + * Updates the preference (on/off) based on whether this app is set as the user's autofill + * service. + */ + fun update() { + switchView?.isChecked = useCases.isEnabled(context) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/DatasetBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/DatasetBuilder.kt new file mode 100644 index 0000000000..ff236c6858 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/DatasetBuilder.kt @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.response.dataset + +import android.content.Context +import android.service.autofill.Dataset +import android.widget.inline.InlinePresentationSpec +import mozilla.components.feature.autofill.AutofillConfiguration + +internal interface DatasetBuilder { + fun build( + context: Context, + configuration: AutofillConfiguration, + imeSpec: InlinePresentationSpec? = null, + ): Dataset +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/LoginDatasetBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/LoginDatasetBuilder.kt new file mode 100644 index 0000000000..24fac38825 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/LoginDatasetBuilder.kt @@ -0,0 +1,206 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.response.dataset + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.app.slice.Slice +import android.content.Context +import android.content.Intent +import android.content.IntentSender +import android.graphics.BlendMode +import android.graphics.drawable.Icon +import android.os.Build +import android.service.autofill.Dataset +import android.service.autofill.Field +import android.service.autofill.InlinePresentation +import android.service.autofill.Presentations +import android.text.TextUtils +import android.view.autofill.AutofillId +import android.view.autofill.AutofillValue +import android.widget.RemoteViews +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import androidx.autofill.inline.UiVersions +import androidx.autofill.inline.v1.InlineSuggestionUi +import mozilla.components.concept.storage.Login +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.handler.EXTRA_LOGIN_ID +import mozilla.components.feature.autofill.structure.ParsedStructure +import mozilla.components.support.utils.PendingIntentUtils + +@RequiresApi(Build.VERSION_CODES.O) +internal data class LoginDatasetBuilder( + val parsedStructure: ParsedStructure, + val login: Login, + val needsConfirmation: Boolean, + val requestOffset: Int = 0, +) : DatasetBuilder { + + @SuppressLint("NewApi") + override fun build( + context: Context, + configuration: AutofillConfiguration, + imeSpec: InlinePresentationSpec?, + ): Dataset { + val dataset = Dataset.Builder() + + val attributionIntent = Intent().apply { + `package` = context.packageName + } + + val pendingIntent = PendingIntent.getActivity( + context, + 0, + attributionIntent, + PendingIntentUtils.defaultFlags or PendingIntent.FLAG_CANCEL_CURRENT, + ) + + val usernameText = login.usernamePresentationOrFallback(context) + val passwordText = login.passwordPresentation(context) + + val usernamePresentation = createViewPresentation(context, usernameText) + val passwordPresentation = createViewPresentation(context, passwordText) + + val usernameInlinePresentation = createInlinePresentation(pendingIntent, imeSpec, usernameText) + val passwordInlinePresentation = createInlinePresentation(pendingIntent, imeSpec, passwordText) + + parsedStructure.usernameId?.let { id -> + dataset.setValue( + id, + if (needsConfirmation) null else AutofillValue.forText(login.username), + usernamePresentation, + usernameInlinePresentation, + ) + } + + parsedStructure.passwordId?.let { id -> + dataset.setValue( + id, + if (needsConfirmation) null else AutofillValue.forText(login.password), + passwordPresentation, + passwordInlinePresentation, + ) + } + + if (needsConfirmation) { + val confirmIntent = Intent(context, configuration.confirmActivity) + confirmIntent.putExtra(EXTRA_LOGIN_ID, login.guid) + + val intentSender: IntentSender = PendingIntent.getActivity( + context, + configuration.activityRequestCode + requestOffset, + confirmIntent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT, + ).intentSender + + dataset.setAuthentication(intentSender) + } + + return dataset.build() + } +} + +internal fun Login.usernamePresentationOrFallback(context: Context): String { + return username.ifEmpty { + context.getString(mozilla.components.feature.autofill.R.string.mozac_feature_autofill_popup_no_username) + } +} + +private fun Login.passwordPresentation(context: Context): String { + return context.getString( + mozilla.components.feature.autofill.R.string.mozac_feature_autofill_popup_password, + usernamePresentationOrFallback(context), + ) +} + +internal fun createViewPresentation(context: Context, title: String): RemoteViews { + val viewPresentation = RemoteViews(context.packageName, android.R.layout.simple_list_item_1) + viewPresentation.setTextViewText(android.R.id.text1, title) + + return viewPresentation +} + +internal fun createInlinePresentation( + pendingIntent: PendingIntent, + imeSpec: InlinePresentationSpec?, + title: String, + icon: Icon? = null, +): InlinePresentation? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && imeSpec != null && + canUseInlineSuggestions(imeSpec) + ) { + return InlinePresentation( + createSlice(title, attribution = pendingIntent, startIcon = icon), + imeSpec, + false, + ) + } + return null +} + +@SuppressLint("RestrictedApi") +@RequiresApi(Build.VERSION_CODES.R) +internal fun createSlice( + title: CharSequence, + subtitle: CharSequence = "", + startIcon: Icon? = null, + endIcon: Icon? = null, + contentDescription: CharSequence = "", + attribution: PendingIntent, +): Slice { + // Build the content for the v1 UI. + val builder = InlineSuggestionUi.newContentBuilder(attribution) + .setContentDescription(contentDescription) + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title) + } + if (!TextUtils.isEmpty(subtitle)) { + builder.setSubtitle(subtitle) + } + if (startIcon != null) { + startIcon.setTintBlendMode(BlendMode.DST) + builder.setStartIcon(startIcon) + } + if (endIcon != null) { + builder.setEndIcon(endIcon) + } + return builder.build().slice +} + +@RequiresApi(Build.VERSION_CODES.R) +internal fun canUseInlineSuggestions(imeSpec: InlinePresentationSpec): Boolean { + return UiVersions.getVersions(imeSpec.style).contains(UiVersions.INLINE_UI_VERSION_1) +} + +@RequiresApi(Build.VERSION_CODES.O) +internal fun Dataset.Builder.setValue( + id: AutofillId, + value: AutofillValue?, + presentation: RemoteViews, + inlinePresentation: InlinePresentation? = null, +): Dataset.Builder { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val fieldBuilder: Field.Builder = Field.Builder() + if (value != null) { + fieldBuilder.setValue(value) + } + val presentationsBuilder = Presentations.Builder() + presentationsBuilder.setMenuPresentation(presentation) + + if (inlinePresentation != null) { + presentationsBuilder.setInlinePresentation(inlinePresentation) + } + + fieldBuilder.setPresentations(presentationsBuilder.build()) + this.setField(id, fieldBuilder.build()) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && inlinePresentation != null) { + @Suppress("DEPRECATION") + setValue(id, value, presentation, inlinePresentation) + } else { + @Suppress("DEPRECATION") + setValue(id, value, presentation) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/SearchDatasetBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/SearchDatasetBuilder.kt new file mode 100644 index 0000000000..f3eefd165c --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/dataset/SearchDatasetBuilder.kt @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.response.dataset + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.IntentSender +import android.os.Build +import android.service.autofill.Dataset +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.R +import mozilla.components.feature.autofill.handler.MAX_LOGINS +import mozilla.components.feature.autofill.structure.ParsedStructure + +@RequiresApi(Build.VERSION_CODES.O) +internal data class SearchDatasetBuilder( + val parsedStructure: ParsedStructure, +) : DatasetBuilder { + + @SuppressLint("NewApi") + override fun build( + context: Context, + configuration: AutofillConfiguration, + imeSpec: InlinePresentationSpec?, + ): Dataset { + val dataset = Dataset.Builder() + + val searchIntent = Intent(context, configuration.searchActivity) + val searchPendingIntent = PendingIntent.getActivity( + context, + configuration.activityRequestCode + MAX_LOGINS, + searchIntent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT, + ) + val intentSender: IntentSender = searchPendingIntent.intentSender + + val title = context.getString( + R.string.mozac_feature_autofill_search_suggestions, + configuration.applicationName, + ) + + val usernamePresentation = createViewPresentation(context, title) + val passwordPresentation = createViewPresentation(context, title) + + val usernameInlinePresentation = createInlinePresentation(searchPendingIntent, imeSpec, title) + val passwordInlinePresentation = createInlinePresentation(searchPendingIntent, imeSpec, title) + + parsedStructure.usernameId?.let { id -> + dataset.setValue( + id, + null, + usernamePresentation, + usernameInlinePresentation, + ) + } + + parsedStructure.passwordId?.let { id -> + dataset.setValue( + id, + null, + passwordPresentation, + passwordInlinePresentation, + ) + } + + dataset.setAuthentication(intentSender) + + return dataset.build() + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt new file mode 100644 index 0000000000..c65ddac63e --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/AuthFillResponseBuilder.kt @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.response.fill + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.IntentSender +import android.graphics.drawable.Icon +import android.os.Build +import android.os.Parcel +import android.service.autofill.FillResponse +import android.service.autofill.InlinePresentation +import android.service.autofill.Presentations +import android.view.autofill.AutofillId +import android.widget.RemoteViews +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.R +import mozilla.components.feature.autofill.response.dataset.createInlinePresentation +import mozilla.components.feature.autofill.structure.ParsedStructure +import mozilla.components.feature.autofill.ui.AbstractAutofillUnlockActivity +import androidx.biometric.R as biometricR + +internal data class AuthFillResponseBuilder( + private val parsedStructure: ParsedStructure, + private val maxSuggestionCount: Int, +) : FillResponseBuilder { + + @SuppressLint("NewApi") + @RequiresApi(Build.VERSION_CODES.O) + override fun build( + context: Context, + configuration: AutofillConfiguration, + imeSpec: InlinePresentationSpec?, + ): FillResponse { + val builder = FillResponse.Builder() + + val autofillIds = listOfNotNull(parsedStructure.usernameId, parsedStructure.passwordId) + + val title = context.getString( + R.string.mozac_feature_autofill_popup_unlock_application, + configuration.applicationName, + ) + + val authPresentation = RemoteViews(context.packageName, android.R.layout.simple_list_item_1).apply { + setTextViewText( + android.R.id.text1, + title, + ) + } + + val authIntent = Intent(context, configuration.unlockActivity) + + // Pass `ParsedStructure` as raw bytes to prevent the system throwing a ClassNotFoundException + // when updating the PendingIntent and trying to create and remap `ParsedStructure` + // from the parcelable extra because of an unknown ClassLoader. + with(Parcel.obtain()) { + parsedStructure.writeToParcel(this, 0) + + authIntent.putExtra( + AbstractAutofillUnlockActivity.EXTRA_PARSED_STRUCTURE, + this.marshall(), + ) + + recycle() + } + + authIntent.putExtra(AbstractAutofillUnlockActivity.EXTRA_IME_SPEC, imeSpec) + authIntent.putExtra( + AbstractAutofillUnlockActivity.EXTRA_MAX_SUGGESTION_COUNT, + maxSuggestionCount, + ) + val authPendingIntent = PendingIntent.getActivity( + context, + configuration.activityRequestCode, + authIntent, + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE, + ) + val intentSender: IntentSender = authPendingIntent.intentSender + + val icon: Icon = Icon.createWithResource( + context, + biometricR.drawable.fingerprint_dialog_fp_icon, + ) + val authInlinePresentation = createInlinePresentation(authPendingIntent, imeSpec, title, icon) + builder.setAuthentication( + autofillIds.toTypedArray(), + intentSender, + authInlinePresentation, + authPresentation, + ) + + return builder.build() + } +} + +@RequiresApi(Build.VERSION_CODES.O) +internal fun FillResponse.Builder.setAuthentication( + ids: Array, + authentication: IntentSender, + inlinePresentation: InlinePresentation? = null, + presentation: RemoteViews, +): FillResponse.Builder { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val presentations: Presentations.Builder = Presentations.Builder() + presentations.apply { + inlinePresentation?.let { + setInlinePresentation(it) + } + setMenuPresentation(presentation) + } + setAuthentication(ids, authentication, presentations.build()) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + @Suppress("DEPRECATION") + setAuthentication(ids, authentication, presentation, inlinePresentation) + } else { + @Suppress("DEPRECATION") + setAuthentication(ids, authentication, presentation) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/FillResponseBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/FillResponseBuilder.kt new file mode 100644 index 0000000000..28d28011eb --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/FillResponseBuilder.kt @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.response.fill + +import android.content.Context +import android.service.autofill.FillResponse +import android.widget.inline.InlinePresentationSpec +import mozilla.components.feature.autofill.AutofillConfiguration + +internal interface FillResponseBuilder { + fun build( + context: Context, + configuration: AutofillConfiguration, + imeSpec: InlinePresentationSpec? = null, + ): FillResponse +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/LoginFillResponseBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/LoginFillResponseBuilder.kt new file mode 100644 index 0000000000..27730c9622 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/response/fill/LoginFillResponseBuilder.kt @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.response.fill + +import android.content.Context +import android.os.Build +import android.service.autofill.FillResponse +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import mozilla.components.concept.storage.Login +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.response.dataset.LoginDatasetBuilder +import mozilla.components.feature.autofill.response.dataset.SearchDatasetBuilder +import mozilla.components.feature.autofill.structure.ParsedStructure + +/** + * [FillResponseBuilder] implementation that creates a [FillResponse] containing logins for + * autofilling. + */ +@RequiresApi(Build.VERSION_CODES.O) +internal data class LoginFillResponseBuilder( + val parsedStructure: ParsedStructure, + val logins: List, + val needsConfirmation: Boolean, +) : FillResponseBuilder { + private val searchDatasetBuilder = SearchDatasetBuilder(parsedStructure) + + override fun build( + context: Context, + configuration: AutofillConfiguration, + imeSpec: InlinePresentationSpec?, + ): FillResponse { + val builder = FillResponse.Builder() + + logins.forEachIndexed { index, login -> + val datasetBuilder = LoginDatasetBuilder( + parsedStructure, + login, + needsConfirmation, + requestOffset = index, + ) + + val dataset = datasetBuilder.build( + context, + configuration, + imeSpec, + ) + + builder.addDataset(dataset) + } + + builder.addDataset( + searchDatasetBuilder.build(context, configuration, imeSpec), + ) + + return builder.build() + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt new file mode 100644 index 0000000000..ebf30869c8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructure.kt @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.structure + +import android.content.Context +import android.os.Build +import android.os.Parcel +import android.os.Parcelable +import android.os.Parcelable.Creator +import android.view.autofill.AutofillId +import androidx.annotation.RequiresApi +import mozilla.components.lib.publicsuffixlist.PublicSuffixList +import mozilla.components.support.utils.Browsers + +/** + * Parsed structure from an autofill request. + * + * Originally implemented in Lockwise: + * https://github.com/mozilla-lockwise/lockwise-android/blob/d3c0511f73c34e8759e1bb597f2d3dc9bcc146f0/app/src/main/java/mozilla/lockbox/autofill/ParsedStructure.kt#L52 + */ +@RequiresApi(Build.VERSION_CODES.O) +data class ParsedStructure( + val usernameId: AutofillId? = null, + val passwordId: AutofillId? = null, + val webDomain: String? = null, + val packageName: String, +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readParcelableCompat(AutofillId::class.java), + parcel.readParcelableCompat(AutofillId::class.java), + parcel.readString(), + parcel.readString() ?: "", + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(usernameId, flags) + parcel.writeParcelable(passwordId, flags) + parcel.writeString(webDomain) + parcel.writeString(packageName) + } + + override fun describeContents(): Int { + return 0 + } + + /** + * Create instances of [ParsedStructure] from a [Parcel]. + */ + companion object CREATOR : Creator { + override fun createFromParcel(parcel: Parcel): ParsedStructure { + return ParsedStructure(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +/** + * Try to find a domain in the [ParsedStructure] for looking up logins. This is either a "web domain" + * for web content the third-party app is displaying (e.g. in a WebView) or the package name of the + * application transformed into a domain. In any case the [publicSuffixList] will be used to turn + * the domain into a "base" domain (public suffix + 1) before returning. + */ +internal suspend fun ParsedStructure.getLookupDomain(publicSuffixList: PublicSuffixList): String { + println("Lookup: webDomain=$webDomain, packageName=$packageName") + val domain = if (webDomain != null && Browsers.isBrowser(packageName)) { + // If the application we are auto-filling is a known browser and it provided a webDomain + // for the content it is displaying then we try to autofill for that. + webDomain + } else { + // We reverse the package name in the hope that this will resemble a domain name. This is + // of course fragile. So we want to find better mechanisms in the future (e.g. looking up + // what URLs the application registers intent handlers for). + packageName.split('.').asReversed().joinToString(".") + } + + return publicSuffixList.getPublicSuffixPlusOne(domain).await() ?: domain +} + +@RequiresApi(Build.VERSION_CODES.O) +internal fun parseStructure(context: Context, structure: RawStructure): ParsedStructure? { + val activityPackageName = structure.activityPackageName + if (context.packageName == activityPackageName) { + // We do not autofill our own activities. Browser content will be auto-filled by Gecko. + return null + } + + val nodeNavigator = structure.createNavigator() + val parsedStructure = ParsedStructureBuilder(nodeNavigator).build() + + if (parsedStructure.passwordId == null && parsedStructure.usernameId == null) { + // If we didn't find any password or username fields then there's nothing to autofill for us. + return null + } + + return parsedStructure +} + +internal fun Parcel.readParcelableCompat(clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + readParcelable(clazz.classLoader, clazz) + } else { + @Suppress("DEPRECATION") + readParcelable(clazz.classLoader) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructureBuilder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructureBuilder.kt new file mode 100644 index 0000000000..d6e90f3d05 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ParsedStructureBuilder.kt @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.structure + +import android.os.Build +import android.view.View +import androidx.annotation.RequiresApi + +@RequiresApi(Build.VERSION_CODES.O) +internal class ParsedStructureBuilder( + private val navigator: AutofillNodeNavigator, +) { + fun build(): ParsedStructure { + val formNode = findFocusedForm() + val (usernameId, passwordId) = findAutofillIds(formNode) + val hostnameClue = usernameId ?: passwordId + + return navigator.build( + usernameId, + passwordId, + getWebDomain(hostnameClue), + getPackageName(hostnameClue) ?: navigator.activityPackageName, + ) + } + + private fun findFocusedForm(): ViewNode? { + val focusPath = findMatchedNodeAncestors { + navigator.isFocused(it) + } + + return focusPath?.lastOrNull { + navigator.isHtmlForm(it) + } + } + + private fun findAutofillIds(rootNode: ViewNode?): Pair = + checkForAdjacentFields(rootNode) ?: getUsernameId(rootNode) to getPasswordId(rootNode) + + private fun getUsernameId(rootNode: ViewNode?): AutofillId? { + // how do we localize the "email" and "username"? + return getAutofillIdForKeywords( + rootNode, + listOf( + View.AUTOFILL_HINT_USERNAME, + View.AUTOFILL_HINT_EMAIL_ADDRESS, + "email", + "username", + "user name", + "identifier", + "account_name", + ), + ) + } + + private fun getPasswordId(rootNode: ViewNode?): AutofillId? { + // similar l10n question for password + return getAutofillIdForKeywords(rootNode, listOf(View.AUTOFILL_HINT_PASSWORD, "password")) + } + + private fun getAutofillIdForKeywords(rootNode: ViewNode?, keywords: Collection): AutofillId? { + return checkForNamedTextField(rootNode, keywords) + ?: checkForConsecutiveLabelAndField(rootNode, keywords) + ?: checkForNestedLayoutAndField(rootNode, keywords) + } + + private fun checkForNamedTextField(rootNode: ViewNode?, keywords: Collection): AutofillId? { + return navigator.findFirst(rootNode) { node: ViewNode -> + if (isAutoFillableEditText(node, keywords) || isAutoFillableInputField(node, keywords)) { + navigator.autofillId(node) + } else { + null + } + } + } + + private fun checkForConsecutiveLabelAndField(rootNode: ViewNode?, keywords: Collection): AutofillId? { + return navigator.findFirst(rootNode) { node: ViewNode -> + val childNodes = navigator.childNodes(node) + // check for consecutive views with keywords followed by possible fill locations + for (i in 1.until(childNodes.size)) { + val prevNode = childNodes[i - 1] + val currentNode = childNodes[i] + val id = navigator.autofillId(currentNode) ?: continue + if ( + (navigator.isEditText(currentNode) || navigator.isHtmlInputField(currentNode)) && + containsKeywords(prevNode, keywords) + ) { + return@findFirst id + } + } + null + } + } + + private fun checkForNestedLayoutAndField(rootNode: ViewNode?, keywords: Collection): AutofillId? { + return navigator.findFirst(rootNode) { node: ViewNode -> + val childNodes = navigator.childNodes(node) + + if (childNodes.size != 1) { + return@findFirst null + } + + val child = childNodes[0] + val id = navigator.autofillId(child) ?: return@findFirst null + if ( + (navigator.isEditText(child) || navigator.isHtmlInputField(child)) && + containsKeywords(node, keywords) + ) { + return@findFirst id + } + null + } + } + + private fun checkForAdjacentFields(rootNode: ViewNode?): Pair? { + return navigator.findFirst(rootNode) { node: ViewNode -> + + val childNodes = navigator.childNodes(node) + // XXX we only look at the list of edit texts before the first button. + // This is because we can see the invisible fields, but not that they are + // invisible. https://bugzilla.mozilla.org/show_bug.cgi?id=1592047 + val firstButtonIndex = childNodes.indexOfFirst { navigator.isButton(it) } + + val firstFewNodes = if (firstButtonIndex >= 0) { + childNodes.subList(0, firstButtonIndex) + } else { + childNodes + } + + val inputFields = firstFewNodes.filter { + navigator.isEditText(it) && navigator.autofillId(it) != null && navigator.isVisible(it) + } + + // we must have a minimum of two EditText boxes in order to have a pair. + if (inputFields.size < 2) { + return@findFirst null + } + + for (i in 1.until(inputFields.size)) { + val prevNode = inputFields[i - 1] + val currentNode = inputFields[i] + if (navigator.isPasswordField(currentNode) && navigator.isPasswordField(prevNode).not()) { + return@findFirst navigator.autofillId(prevNode) to navigator.autofillId(currentNode) + } + } + + null + } + } + + private fun getWebDomain(nearby: AutofillId?): String? { + return nearestFocusedNode(nearby) { + navigator.webDomain(it) + } + } + + private fun getPackageName(nearby: AutofillId?): String? { + return nearestFocusedNode(nearby) { + navigator.packageName(it) + } + } + + private fun nearestFocusedNode(nearby: AutofillId?, transform: (ViewNode) -> T?): T? { + val id = nearby ?: return null + val ancestors = findMatchedNodeAncestors { + navigator.autofillId(it) == id + } + return ancestors?.map(transform)?.firstOrNull { it != null } + } + + private fun isAutoFillableEditText(node: ViewNode, keywords: Collection): Boolean { + return navigator.isEditText(node) && + containsKeywords(node, keywords) && + navigator.autofillId(node) != null + } + + private fun isAutoFillableInputField(node: ViewNode, keywords: Collection): Boolean { + return navigator.isHtmlInputField(node) && + containsKeywords(node, keywords) && + navigator.autofillId(node) != null + } + + private fun containsKeywords(node: ViewNode, keywords: Collection): Boolean { + val hints = navigator.clues(node) + keywords.forEach { keyword -> + hints.forEach { hint -> + if (hint.contains(keyword, true)) { + return true + } + } + } + return false + } + + private fun findMatchedNodeAncestors(matcher: (ViewNode) -> Boolean): Iterable? { + navigator.rootNodes + .forEach { node -> + findMatchedNodeAncestors(node, matcher)?.let { result -> + return result + } + } + return null + } + + /** + * Depth first search a ViewNode tree. Once a match is found, a list of ancestors all the way to + * the top is returned. The first node in the list is the matching node, the last is the root node. + * If no match is found, then null is returned. + * + * @param node the parent node. + * @param matcher a closure which returns true if and only if the node is matched. + * @return an ordered list of the matched node and all its ancestors starting at the matched node. + */ + private fun findMatchedNodeAncestors(node: ViewNode, matcher: (ViewNode) -> Boolean): Iterable? { + if (matcher(node)) { + return listOf(node) + } + + navigator.childNodes(node) + .forEach { child -> + findMatchedNodeAncestors(child, matcher)?.let { list -> + return list + node + } + } + return null + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/RawStructure.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/RawStructure.kt new file mode 100644 index 0000000000..1ac3fdfc21 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/RawStructure.kt @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.structure + +import android.app.assist.AssistStructure +import android.os.Build +import android.view.autofill.AutofillId +import androidx.annotation.RequiresApi + +/** + * A raw view structure provided by an application - to be parsed into a [ParsedStructure]. + */ +internal interface RawStructure { + val activityPackageName: String + + fun createNavigator(): AutofillNodeNavigator<*, AutofillId> +} + +@RequiresApi(Build.VERSION_CODES.O) +internal fun AssistStructure.toRawStructure(): RawStructure { + return AssistStructureWrapper(this) +} + +@RequiresApi(Build.VERSION_CODES.O) +private class AssistStructureWrapper( + private val actual: AssistStructure, +) : RawStructure { + override val activityPackageName: String + get() = actual.activityComponent.packageName + + override fun createNavigator(): AutofillNodeNavigator<*, AutofillId> { + return ViewNodeNavigator(actual, activityPackageName) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ViewNodeNavigator.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ViewNodeNavigator.kt new file mode 100644 index 0000000000..5ed794e298 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/structure/ViewNodeNavigator.kt @@ -0,0 +1,186 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.structure + +import android.app.assist.AssistStructure +import android.app.assist.AssistStructure.ViewNode +import android.os.Build +import android.text.InputType +import android.view.View +import android.view.autofill.AutofillId +import androidx.annotation.RequiresApi +import mozilla.components.feature.autofill.structure.AutofillNodeNavigator.Companion.editTextMask +import java.util.Locale + +/** + * Helper for navigating autofill nodes. + * + * Original implementation imported from Lockwise: + * https://github.com/mozilla-lockwise/lockwise-android/blob/f303f8aee7cc96dcdf4e7863fef6c19ae874032e/app/src/main/java/mozilla/lockbox/autofill/ViewNodeNavigator.kt#L13 + */ +internal interface AutofillNodeNavigator { + companion object { + val editTextMask = InputType.TYPE_CLASS_TEXT + val passwordMask = + InputType.TYPE_TEXT_VARIATION_PASSWORD or + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + } + + val rootNodes: List + val activityPackageName: String + fun childNodes(node: Node): List + fun clues(node: Node): Iterable + fun autofillId(node: Node): Id? + fun isEditText(node: Node): Boolean + fun isHtmlInputField(node: Node): Boolean + fun isHtmlForm(node: Node): Boolean + fun packageName(node: Node): String? + fun webDomain(node: Node): String? + fun currentText(node: Node): String? + fun inputType(node: Node): Int + fun isPasswordField(node: Node): Boolean = (inputType(node) and passwordMask) > 0 + fun isButton(node: Node): Boolean + fun isFocused(node: Node): Boolean + fun isVisible(node: Node): Boolean + fun build( + usernameId: Id?, + passwordId: Id?, + webDomain: String?, + packageName: String, + ): ParsedStructure + + private fun findFirstRoots(transform: (Node) -> T?): T? { + rootNodes + .forEach { node -> + findFirst(node, transform)?.let { result -> + return result + } + } + return null + } + + @Suppress("ReturnCount") + fun findFirst(rootNode: Node? = null, transform: (Node) -> T?): T? { + val node = rootNode ?: return findFirstRoots(transform) + + transform(node)?.let { + return it + } + + childNodes(node) + .forEach { child -> + findFirst(child, transform)?.let { result -> + return result + } + } + return null + } +} + +/** + * Helper for navigating autofill nodes. + * + * Original implementation imported from Lockwise: + * https://github.com/mozilla-lockwise/lockwise-android/blob/f303f8aee7cc96dcdf4e7863fef6c19ae874032e/app/src/main/java/mozilla/lockbox/autofill/ViewNodeNavigator.kt#L72 + */ +@RequiresApi(Build.VERSION_CODES.O) +internal class ViewNodeNavigator( + private val structure: AssistStructure, + override val activityPackageName: String, +) : AutofillNodeNavigator { + override val rootNodes: List + get() = structure.run { (0 until windowNodeCount).map { getWindowNodeAt(it).rootViewNode } } + + override fun childNodes(node: ViewNode): List = + node.run { (0 until childCount) }.map { node.getChildAt(it) } + + override fun clues(node: ViewNode): Iterable { + val hints = mutableListOf( + node.text, + node.idEntry, + node.hint, // This is localized. + ) + + node.autofillOptions?.let { + hints.addAll(it) + } + + node.autofillHints?.let { + hints.addAll(it) + } + + node.htmlInfo?.attributes?.let { attrs -> + hints.addAll(attrs.map { it.second }) + } + + return hints.filterNotNull() + } + + override fun autofillId(node: ViewNode): AutofillId? = node.autofillId + + override fun isEditText(node: ViewNode) = + inputType(node) and editTextMask > 0 + + override fun inputType(node: ViewNode) = node.inputType + + override fun isHtmlInputField(node: ViewNode) = + htmlTagName(node) == "input" + + private fun htmlAttr(node: ViewNode, name: String) = + node.htmlInfo?.attributes?.find { name == it.first }?.second + + @Suppress("ReturnCount") + override fun isButton(node: ViewNode): Boolean { + val className = node.className ?: "" + when { + className.contains("Button") -> return true + htmlTagName(node) == "button" -> return true + htmlTagName(node) != "input" -> return false + } + + return when (htmlAttr(node, "type")) { + "submit" -> true + "button" -> true + else -> false + } + } + + private fun htmlTagName(node: ViewNode) = + // Use English locale, as the HTML tags are all in English. + node.htmlInfo?.tag?.lowercase(Locale.ENGLISH) + + override fun isHtmlForm(node: ViewNode) = + htmlTagName(node) == "form" + + override fun isVisible(node: ViewNode) = node.visibility == View.VISIBLE + + override fun packageName(node: ViewNode): String? = node.idPackage + + override fun webDomain(node: ViewNode): String? = node.webDomain + + override fun currentText(node: ViewNode): String? { + return if (node.autofillValue?.isText == true) { + node.autofillValue?.textValue.toString() + } else { + null + } + } + + override fun isFocused(node: ViewNode) = node.isFocused + + override fun build( + usernameId: AutofillId?, + passwordId: AutofillId?, + webDomain: String?, + packageName: String, + ): ParsedStructure { + return ParsedStructure( + usernameId, + passwordId, + webDomain, + packageName, + ) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillConfirmActivity.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillConfirmActivity.kt new file mode 100644 index 0000000000..d32e1a8e4e --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillConfirmActivity.kt @@ -0,0 +1,139 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.ui + +import android.app.Dialog +import android.app.assist.AssistStructure +import android.content.DialogInterface +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.service.autofill.Dataset +import android.view.autofill.AutofillManager +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.R +import mozilla.components.feature.autofill.facts.emitAutofillConfirmationFact +import mozilla.components.feature.autofill.handler.EXTRA_LOGIN_ID +import mozilla.components.feature.autofill.handler.FillRequestHandler +import mozilla.components.feature.autofill.structure.toRawStructure +import mozilla.components.support.utils.ext.getParcelableExtraCompat +import mozilla.components.ui.widgets.withCenterAlignedButtons + +/** + * Activity responsible for asking the user to confirm before autofilling a third-party app. It is + * shown in situations where the authenticity of an application could not be confirmed automatically + * with "Digital Asset Links". + */ +@RequiresApi(Build.VERSION_CODES.O) +abstract class AbstractAutofillConfirmActivity : FragmentActivity() { + abstract val configuration: AutofillConfiguration + + private var dataset: Deferred? = null + private val fillHandler by lazy { FillRequestHandler(context = this, configuration) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val structure: AssistStructure? = intent.getParcelableExtraCompat( + AutofillManager.EXTRA_ASSIST_STRUCTURE, + AssistStructure::class.java, + ) + val loginId = intent.getStringExtra(EXTRA_LOGIN_ID) + if (loginId == null) { + cancel() + return + } + val imeSpec = intent.getImeSpec() + // While the user is asked to confirm, we already try to build the fill response asynchronously. + val rawStructure = structure?.toRawStructure() + if (rawStructure != null) { + dataset = lifecycleScope.async(Dispatchers.IO) { + val builder = fillHandler.handleConfirmation(rawStructure, loginId) + builder?.build(this@AbstractAutofillConfirmActivity, configuration, imeSpec) + } + } + + if (savedInstanceState == null) { + val fragment = AutofillConfirmFragment() + fragment.show(supportFragmentManager, "confirm_fragment") + } + } + + /** + * Confirms the autofill request and returns the credentials to the autofill framework. + */ + internal fun confirm() { + val replyIntent = Intent().apply { + // At this point it should be safe to block since the fill response should be ready once + // the user has authenticated. + runBlocking { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset?.await()) } + } + + emitAutofillConfirmationFact(confirmed = true) + + setResult(RESULT_OK, replyIntent) + finish() + } + + /** + * Cancels the autofill request. + */ + internal fun cancel() { + dataset?.cancel() + + emitAutofillConfirmationFact(confirmed = false) + + setResult(RESULT_CANCELED) + finish() + } +} + +@RequiresApi(Build.VERSION_CODES.O) +internal class AutofillConfirmFragment : DialogFragment() { + private val configuration: AutofillConfiguration + get() = getConfirmActivity().configuration + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return AlertDialog.Builder(requireContext()) + .setTitle( + getString(R.string.mozac_feature_autofill_confirmation_title), + ) + .setMessage( + getString(R.string.mozac_feature_autofill_confirmation_authenticity, configuration.applicationName), + ) + .setPositiveButton(R.string.mozac_feature_autofill_confirmation_yes) { _, _ -> confirmRequest() } + .setNegativeButton(R.string.mozac_feature_autofill_confirmation_no) { _, _ -> cancelRequest() } + .create() + .withCenterAlignedButtons() + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + cancelRequest() + } + + private fun confirmRequest() { + getConfirmActivity() + .confirm() + } + + private fun cancelRequest() { + getConfirmActivity() + .cancel() + } + + private fun getConfirmActivity(): AbstractAutofillConfirmActivity { + return requireActivity() as AbstractAutofillConfirmActivity + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillSearchActivity.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillSearchActivity.kt new file mode 100644 index 0000000000..88f5c461a8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillSearchActivity.kt @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.ui + +import android.app.assist.AssistStructure +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.autofill.AutofillManager +import android.widget.EditText +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.FragmentActivity +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import mozilla.components.concept.storage.Login +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.R +import mozilla.components.feature.autofill.facts.emitAutofillSearchDisplayedFact +import mozilla.components.feature.autofill.facts.emitAutofillSearchSelectedFact +import mozilla.components.feature.autofill.facts.emitLoginPasswordDetectedFact +import mozilla.components.feature.autofill.response.dataset.LoginDatasetBuilder +import mozilla.components.feature.autofill.structure.ParsedStructure +import mozilla.components.feature.autofill.structure.parseStructure +import mozilla.components.feature.autofill.structure.toRawStructure +import mozilla.components.feature.autofill.ui.search.LoginsAdapter +import mozilla.components.support.ktx.android.view.showKeyboard +import mozilla.components.support.utils.ext.getParcelableExtraCompat + +/** + * Activity responsible for letting the user manually search and pick credentials for auto-filling a + * third-party app. + */ +@RequiresApi(Build.VERSION_CODES.O) +abstract class AbstractAutofillSearchActivity : FragmentActivity() { + abstract val configuration: AutofillConfiguration + + private lateinit var parsedStructure: ParsedStructure + private lateinit var loginsDeferred: Deferred> + private val scope = CoroutineScope(Dispatchers.IO) + private val adapter = LoginsAdapter(::onLoginSelected) + private var imeSpec: InlinePresentationSpec? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (savedInstanceState == null) { + emitAutofillSearchDisplayedFact() + } + + val structure: AssistStructure? = + intent.getParcelableExtraCompat(AutofillManager.EXTRA_ASSIST_STRUCTURE, AssistStructure::class.java) + if (structure == null) { + finish() + return + } + imeSpec = intent.getImeSpec() + + val parsedStructure = parseStructure(this, structure.toRawStructure()) + if (parsedStructure == null) { + finish() + return + } + + this.parsedStructure = parsedStructure + this.loginsDeferred = loadAsync() + + setContentView(R.layout.mozac_feature_autofill_search) + + val recyclerView = findViewById(R.id.mozac_feature_autofill_list) + recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + recyclerView.adapter = adapter + recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL)) + + val searchView = findViewById(R.id.mozac_feature_autofill_search) + searchView.doOnTextChanged { text, _, _, _ -> + if (text != null && text.isNotEmpty()) { + performSearch(text) + } else { + clearResults() + } + } + + searchView.showKeyboard() + } + + private fun onLoginSelected(login: Login) { + val builder = LoginDatasetBuilder(parsedStructure, login, needsConfirmation = false) + val dataset = builder.build(this, configuration, imeSpec) + + val replyIntent = Intent() + replyIntent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset) + + emitAutofillSearchSelectedFact() + + setResult(RESULT_OK, replyIntent) + finish() + } + + override fun onDestroy() { + super.onDestroy() + scope.cancel() + } + + private fun performSearch(text: CharSequence) = scope.launch { + val logins = loginsDeferred.await() + + val filteredLogins = logins.filter { login -> + login.username.contains(text) || + login.origin.contains(text) + } + + if (filteredLogins.isNotEmpty() && + filteredLogins[0].password.isNotEmpty() + ) { + emitLoginPasswordDetectedFact() + } + + withContext(Dispatchers.Main) { + adapter.update(filteredLogins) + } + } + + private fun clearResults() { + adapter.clear() + } + + private fun loadAsync(): Deferred> { + return scope.async { + configuration.storage.list() + } + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt new file mode 100644 index 0000000000..33586aac72 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/AbstractAutofillUnlockActivity.kt @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.ui + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Parcel +import android.service.autofill.FillResponse +import android.view.autofill.AutofillManager +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.RequiresApi +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.authenticator.Authenticator +import mozilla.components.feature.autofill.authenticator.createAuthenticator +import mozilla.components.feature.autofill.facts.emitAutofillLock +import mozilla.components.feature.autofill.handler.FillRequestHandler +import mozilla.components.feature.autofill.handler.MAX_LOGINS +import mozilla.components.feature.autofill.structure.ParsedStructure +import mozilla.components.support.utils.ext.getParcelableExtraCompat + +/** + * Activity responsible for unlocking the autofill service by asking the user to verify with a + * fingerprint or alternative device unlocking mechanism. + */ +@RequiresApi(Build.VERSION_CODES.O) +abstract class AbstractAutofillUnlockActivity : FragmentActivity() { + abstract val configuration: AutofillConfiguration + + private var fillResponse: Deferred? = null + private val fillHandler by lazy { FillRequestHandler(context = this, configuration) } + private val authenticator: Authenticator? by lazy { createAuthenticator(this, configuration) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val parsedStructure = with(Parcel.obtain()) { + val rawBytes = intent.getByteArrayExtra(EXTRA_PARSED_STRUCTURE) + unmarshall(rawBytes!!, 0, rawBytes.size) + setDataPosition(0) + ParsedStructure(this).also { + recycle() + } + } + val imeSpec = intent.getImeSpec() + val maxSuggestionCount = intent.getIntExtra(EXTRA_MAX_SUGGESTION_COUNT, MAX_LOGINS) + // While the user is asked to authenticate, we already try to build the fill response asynchronously. + fillResponse = lifecycleScope.async(Dispatchers.IO) { + val builder = fillHandler.handle(parsedStructure, forceUnlock = true, maxSuggestionCount) + val result = builder.build(this@AbstractAutofillUnlockActivity, configuration, imeSpec) + result + } + + if (authenticator == null) { + // If no authenticator is available then we just bail here. Instead we should ask the user to + // enroll, or show an error message instead. + // https://github.com/mozilla-mobile/android-components/issues/9756 + setResult(RESULT_CANCELED) + finish() + } else { + authenticator!!.prompt(this, PromptCallback()) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + authenticator?.onActivityResult(requestCode, resultCode) + } + + internal inner class PromptCallback : Authenticator.Callback { + override fun onAuthenticationError() { + fillResponse?.cancel() + + emitAutofillLock(unlocked = false) + + setResult(RESULT_CANCELED) + finish() + } + + override fun onAuthenticationSucceeded() { + configuration.lock.unlock() + + val replyIntent = Intent().apply { + // At this point it should be safe to block since the fill response should be ready once + // the user has authenticated. + runBlocking { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse?.await()) } + } + + emitAutofillLock(unlocked = true) + + setResult(RESULT_OK, replyIntent) + finish() + } + + override fun onAuthenticationFailed() { + setResult(RESULT_CANCELED) + finish() + } + } + + companion object { + const val EXTRA_PARSED_STRUCTURE = "parsed_structure" + const val EXTRA_IME_SPEC = "ime_spec" + const val EXTRA_MAX_SUGGESTION_COUNT = "max_suggestion_count" + } +} + +internal fun Intent.getImeSpec(): InlinePresentationSpec? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + getParcelableExtraCompat( + AbstractAutofillUnlockActivity.EXTRA_IME_SPEC, + InlinePresentationSpec::class.java, + ) + } else { + null + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginViewHolder.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginViewHolder.kt new file mode 100644 index 0000000000..5d9ecbf771 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginViewHolder.kt @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.ui.search + +import android.view.View +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import mozilla.components.concept.storage.Login +import mozilla.components.feature.autofill.R +import mozilla.components.feature.autofill.response.dataset.usernamePresentationOrFallback + +/** + * ViewHolder for a login item in the autofill search view. + */ +internal class LoginViewHolder( + itemView: View, + private val onLoginSelected: (Login) -> Unit, +) : RecyclerView.ViewHolder(itemView) { + private val usernameView = itemView.findViewById(R.id.mozac_feature_autofill_username) + private val originView = itemView.findViewById(R.id.mozac_feature_autofill_origin) + + fun bind(login: Login) { + usernameView.text = login.usernamePresentationOrFallback(itemView.context) + originView.text = login.origin + + itemView.setOnClickListener { onLoginSelected(login) } + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginsAdapter.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginsAdapter.kt new file mode 100644 index 0000000000..54d65b0e88 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/ui/search/LoginsAdapter.kt @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.ui.search + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import mozilla.components.concept.storage.Login +import mozilla.components.feature.autofill.R + +/** + * Adapter for showing a list of logins. + */ +@SuppressLint("NotifyDataSetChanged") +internal class LoginsAdapter( + private val onLoginSelected: (Login) -> Unit, +) : RecyclerView.Adapter() { + private var logins: List = emptyList() + + fun update(logins: List) { + this.logins = logins + notifyDataSetChanged() + } + + fun clear() { + this.logins = emptyList() + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LoginViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.mozac_feature_autofill_login, parent, false) + return LoginViewHolder(view, onLoginSelected) + } + + override fun onBindViewHolder(holder: LoginViewHolder, position: Int) { + val login = logins[position] + holder.bind(login) + } + + override fun getItemCount(): Int { + return logins.count() + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/verify/CredentialAccessVerifier.kt b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/verify/CredentialAccessVerifier.kt new file mode 100644 index 0000000000..627f0ca72b --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/java/mozilla/components/feature/autofill/verify/CredentialAccessVerifier.kt @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.verify + +import android.content.Context +import mozilla.components.service.digitalassetlinks.AndroidAssetFinder +import mozilla.components.service.digitalassetlinks.AssetDescriptor +import mozilla.components.service.digitalassetlinks.Relation +import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker + +/** + * Helper to verify that a specific application is allowed to receive get the login credentials for + * a specific domain. + * + * The verification is done through Digital Asset Links, which allow a domain to specify associated + * apps and their signatures. + * - https://developers.google.com/digital-asset-links/v1/getting-started + * - https://github.com/google/digitalassetlinks/blob/master/well-known/details.md + */ +class CredentialAccessVerifier( + private val checker: StatementRelationChecker, + private val assetsFinder: AndroidAssetFinder = AndroidAssetFinder(), +) { + /** + * Verifies and returns `true` if the application with [packageName] is allowed to receive + * credentials for [domain] according to the hosted Digital Assets Links file. Returns `false` + * otherwise. This method may also return `false` if a verification could not be performed, + * e.g. the device is offline. + */ + fun hasCredentialRelationship( + context: Context, + domain: String, + packageName: String, + ): Boolean { + val assets = assetsFinder.getAndroidAppAsset(packageName, context.packageManager).toList() + + // I was expecting us to need to verify all signatures here. But If I understand the usage + // in `OriginVerifier` and the spec (see link in class comment) correctly then verifying one + // certificate is enough to identify an app. + val asset = assets.firstOrNull() ?: return false + + return checker.checkRelationship( + AssetDescriptor.Web("https://$domain"), + Relation.GET_LOGIN_CREDS, + asset, + ) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_login.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_login.xml new file mode 100644 index 0000000000..c20c994cd8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_login.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_preference.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_preference.xml new file mode 100644 index 0000000000..a5ef02eae2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_preference.xml @@ -0,0 +1,11 @@ + + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_search.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_search.xml new file mode 100644 index 0000000000..cdefaa8152 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/layout/mozac_feature_autofill_search.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-am/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-am/strings.xml new file mode 100644 index 0000000000..3f9ad47753 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-am/strings.xml @@ -0,0 +1,45 @@ + + + + %1$sን ይክፈቱ + + + (የተጠቃሚ ስም የለም) + + + የይለፍ ቃል ለ%1$s + + + ማረጋገጥ አልተሳካም + + + %1$s የመተግበሪያውን ትክክለኛነት ማረጋገጥ አልቻለም። የተመረጡትን ምስክርነቶች በራስ-ሙላ መቀጠል ይፈልጋሉ? + + + አዎ + + + ይቅር + + + %1$s ፈልግ + + + መግቢያዎችን ፈልግ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ar/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..2826ba8e9d --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ar/strings.xml @@ -0,0 +1,46 @@ + + + + + ألغِ قفل %1$s + + + (لا اسم مستخدم) + + + كلمة سر %1$s + + + فشل التحقق + + + لم يتمكّن %1$s من التحقّق من صحّة التطبيق. أتريد مواصلة ملء بيانات الولوج المحدّدة؟ + + + نعم + + + لا + + + ابحث عن %1$s + + + ابحث في جلسات الولوج + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ast/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ast/strings.xml new file mode 100644 index 0000000000..07637c3330 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ast/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquiar «%1$s» + + + (Ensin nome d\'usuariu) + + + Contraseña de: %1$s + + + La verificación falló + + + «%1$s» nun pudo verificar l\'autenticidá de l\'aplicación. ¿Quies siguir col rellenu automáticu de los datos seleicionaos? + + + + + + Non + + + Buscar «%1$s» + + + Buscar nes cuentes + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-azb/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-azb/strings.xml new file mode 100644 index 0000000000..e231f52438 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-azb/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s کیلیدینی آچ + + + ( هئچ قوللانیجی آدی یوخ) + + + %1$s اوچون رمز + + + دوغرولاما قیریلدی + + + %1$s اپلیکیشن‌ین صحتینی تایید ائلینمه‌دی. سئچیلن کیملیک بیلگی‌لرینی اوتوماتیک دولدورماغا دوام ائتمک ایستییرسیز؟ + + + هن + + + یوخ + + + %1$s -دا آختار + + + گیریش‌لرده آختار + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-be/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-be/strings.xml new file mode 100644 index 0000000000..7ba8d819bb --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-be/strings.xml @@ -0,0 +1,45 @@ + + + + Разблакаваць %1$s + + + (Без імя карыстальніка) + + + Пароль для %1$s + + + Памылка праверкі + + + %1$s не ўдалося пацвердзіць сапраўднасць праграмы. Вы хочаце працягнуць аўтазапаўненне выбраных уліковых дадзеных? + + + Так + + + Не + + + Шукаць у %1$s + + + Шукаць лагіны + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-bg/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..f80a6023d4 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-bg/strings.xml @@ -0,0 +1,46 @@ + + + + + Отключете %1$s + + + (без потребителско име) + + + Парола за %1$s + + + Удостоверяването е неуспешно + + + %1$s не успя да потвърди автентичността на приложението. Желаете ли потребителските данни да бъдат попълнени автоматично въпреки това? + + + Да + + + Не + + + Търсене с %1$s + + + Търсене на регистрация + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-br/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-br/strings.xml new file mode 100644 index 0000000000..45f6647fde --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-br/strings.xml @@ -0,0 +1,45 @@ + + + + Dibrennañ %1$s + + + (anv arveriad ebet) + + + Ger-tremen evit %1$s + + + Gwiriadur cʼhwitet + + + %1$s nʼen deus ket gellet gwiriañ dilested an arloadoù. Hag e fell deocʼh leuniañ an titouroù kennaskañ diuzet en un doare emgefreek? + + + Ya + + + Ket + + + Klask %1$s + + + Klask titouroù kennaskañ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-bs/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-bs/strings.xml new file mode 100644 index 0000000000..132dde06d1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-bs/strings.xml @@ -0,0 +1,45 @@ + + + + Otključaj %1$s + + + (Nema korisničkog imena) + + + Lozinka za %1$s + + + Provjera nije uspjela + + + %1$s nije mogao provjeriti autentičnost aplikacije. Želite li nastaviti s automatskim popunjavanjem odabranih akreditacija? + + + Da + + + Ne + + + Pretraži %1$s + + + Pretraži prijave + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ca/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000000..67c5b2fde2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ca/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloca el %1$s + + + (Cap nom d’usuari) + + + Contrasenya per a %1$s + + + Ha fallat la verificació + + + El %1$s no ha pogut verificar l’autenticitat de l’aplicació. Voleu procedir amb l’emplenament automàtic de les credencials seleccionades? + + + + + + No + + + Cerca al %1$s + + + Cerca els inicis de sessió + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-cak/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-cak/strings.xml new file mode 100644 index 0000000000..3581e1b4a4 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-cak/strings.xml @@ -0,0 +1,45 @@ + + + + Timeq\'at %1$s + + + (Majun rub\'i\' okisanel) + + + Ewan tzij richin %1$s + + + Xsach jikib\'anïk + + + %1$s man xtikïr ta xnik\'öx ri rujikib\'axik chokoy. ¿La nawajo\' chi ruyon ketz\'aqatisäx ri taq ruwujil echa\'on? + + + Ja\' + + + Mani + + + Tikanöx %1$s + + + Kekanöx tikirib\'äl taq molojri\'ïl + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ceb/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ceb/strings.xml new file mode 100644 index 0000000000..210f13dcd3 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ceb/strings.xml @@ -0,0 +1,45 @@ + + + + Unlock %1$s + + + (Wala\'y username) + + + Password sa %1$s + + + Verification napakyas + + + Ang %1$s dili maka-suta sa katinuod sa application. Buot mo ba i-padayon sa pag-autofill ang mga napiling mga credential? + + + Oo + + + Dili + + + Pangitaon %1$s + + + Pangitaon ang logins + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ckb/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ckb/strings.xml new file mode 100644 index 0000000000..0048681436 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ckb/strings.xml @@ -0,0 +1,45 @@ + + + + کردنەوەی %1$s + + + ( بێ ناوی بەکارهێنەر) + + + وشەی تێپەڕبوون بۆ %1$s + + + دڵنیابوونەوە سەرکەوتوو نەبوو + + + %1$s ناتوانێت دڵنیابێت لە دەسەڵات دان بەم بەرنامەیە. ئایا دەتەوێت بەردەوام بیت لە پێدانی دەسەڵاتی پڕکردنەوەی خۆکار زانیارییەکانت لەم ماڵپەڕە؟ + + + بەڵێ + + + نەخێر + + + بگەڕێ بۆ %1$s + + + بگەڕێ لە ناو چوونەژوورەوەکان + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-co/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-co/strings.xml new file mode 100644 index 0000000000..55b9e5cbbd --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-co/strings.xml @@ -0,0 +1,45 @@ + + + + Spalancà %1$s + + + (Nisunu nome d’utilizatore) + + + Parolla d’intesa per %1$s + + + Verificazione fiascata + + + %1$s ùn pò micca verificà l’autenticità di l’appiecazione. Vulete cuntinuà à riempie autumaticamente l’identificazioni di cunnessione selezziunate ? + + + + + + + + + Ricercà in %1$s + + + Ricercà identificazioni di cunnessione + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-cs/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000000..d0053761c2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-cs/strings.xml @@ -0,0 +1,45 @@ + + + + Odemknout aplikaci %1$s + + + (Žádné uživatelské jméno) + + + Heslo pro účet %1$s + + + Ověření se nezdařilo + + + Aplikace %1$s nemohla ověřit cílovou aplikaci. Opravdu do ní chcete vložit vybrané přihlašovací údaje? + + + Ano + + + Ne + + + Hledat v aplikaci %1$s + + + Hledat přihlašovací údaje + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-cy/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-cy/strings.xml new file mode 100644 index 0000000000..9e940fe253 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-cy/strings.xml @@ -0,0 +1,45 @@ + + + + Datgloi %1$s + + + (dim enw defnyddiwr) + + + Cyfrinair %1$s + + + Methodd y dilysu + + + Nid oedd %1$s yn gallu gwirio dilysrwydd y rhaglen. Ydych chi am fwrw ymlaen ag awtolenwi’r tystlythyrau a ddewiswyd? + + + Iawn + + + Na + + + Chwilio %1$s + + + Chwilio mewngofnodion + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-da/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-da/strings.xml new file mode 100644 index 0000000000..ecdb268534 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-da/strings.xml @@ -0,0 +1,45 @@ + + + + Lås %1$s op + + + (Intet brugernavn) + + + Adgangskode for %1$s + + + Bekræftelse mislykkedes + + + %1$s kunne ikke bekræfte autenticiteten af applikationen. Vil du fortsætte med at autofylde de valgte login-informationer? + + + Ja + + + Nej + + + Søg med %1$s + + + Søg efter logins + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-de/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-de/strings.xml new file mode 100644 index 0000000000..ffc1c79c77 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-de/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s entsperren + + + (Kein Benutzername) + + + Passwort für %1$s + + + Verifizierung fehlgeschlagen + + + %1$s konnte die Authentizität der Anwendung nicht überprüfen. Möchten Sie mit der Autovervollständigung der ausgewählten Anmeldeinformationen fortfahren? + + + Ja + + + Nein + + + %1$s durchsuchen + + + Zugangsdaten durchsuchen + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-dsb/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-dsb/strings.xml new file mode 100644 index 0000000000..c68ae83486 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-dsb/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s wěcej njeblokěrowaś + + + (Žedno wužywaŕske mě) + + + Gronidło za %1$s + + + Pśeglědanje njejo se raźiło + + + %1$s njejo mógał awtentiskosć nałoženja pśespytowaś. Cośo z awtomatiskim wupołnjowanim wubranych pśizjawjeńskich datow pókšacowaś? + + + Jo + + + + + + %1$s pśepytaś + + + Pśizjawjenja pytaś + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-el/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-el/strings.xml new file mode 100644 index 0000000000..3192342b51 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-el/strings.xml @@ -0,0 +1,45 @@ + + + + Ξεκλείδωμα %1$s + + + (Χωρίς όνομα χρήστη) + + + Κωδικός πρόσβασης για %1$s + + + Αποτυχία επαλήθευσης + + + Το %1$s δεν μπόρεσε να επαληθεύσει την αυθεντικότητα της εφαρμογής. Θέλετε να γίνει αυτόματη συμπλήρωση των επιλεγμένων διαπιστευτηρίων; + + + Ναι + + + Όχι + + + Αναζήτηση %1$s + + + Αναζήτηση συνδέσεων + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rCA/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rCA/strings.xml new file mode 100644 index 0000000000..31b82b38e8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rCA/strings.xml @@ -0,0 +1,45 @@ + + + + Unlock %1$s + + + (No username) + + + Password for %1$s + + + Verification failed + + + %1$s could not verify the authenticity of the application. Do you want to proceed with autofilling the selected credentials? + + + Yes + + + No + + + Search %1$s + + + Search logins + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rGB/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000..31b82b38e8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,45 @@ + + + + Unlock %1$s + + + (No username) + + + Password for %1$s + + + Verification failed + + + %1$s could not verify the authenticity of the application. Do you want to proceed with autofilling the selected credentials? + + + Yes + + + No + + + Search %1$s + + + Search logins + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-eo/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-eo/strings.xml new file mode 100644 index 0000000000..41e4760af1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-eo/strings.xml @@ -0,0 +1,46 @@ + + + + + Malbloki %1$s + + + (sen nomo de uzanto) + + + Pasvorto por %1$s + + + Malsukcesa kontrolo + + + %1$s ne povis kontroli la aŭtentikecon de la programo. Ĉu vi volas daŭrigi la aŭtomatan plenigadon de la elektitaj legitimiloj? + + + Jes + + + Ne + + + Serĉi %1$s + + + Serĉi legitimilojn + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rAR/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rAR/strings.xml new file mode 100644 index 0000000000..6e6c053344 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rAR/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sin nombre de usuario) + + + Contraseña para %1$s + + + Falló la verificación + + + %1$s no pudo verificar la autenticidad de la aplicación. ¿Desea continuar autocompletando las credenciales seleccionadas? + + + + + + No + + + Buscar en %1$s + + + Buscar inicios de sesión + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rCL/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rCL/strings.xml new file mode 100644 index 0000000000..2a11a588b4 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rCL/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sin nombre de usuario) + + + Contraseña para %1$s + + + Verificación fallida + + + %1$s no pudo verificar la autenticidad de la aplicación. ¿Deseas continuar autocompletando las credenciales seleccionadas? + + + + + + No + + + Buscar %1$s + + + Buscar credenciales + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rES/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rES/strings.xml new file mode 100644 index 0000000000..925d071c4a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rES/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sin nombre de usuario) + + + Contraseña para %1$s + + + Ha fallado la verificación + + + %1$s no ha podido verificar la autenticidad de la aplicación. ¿Quieres continuar autocompletando las credenciales seleccionadas? + + + + + + No + + + Buscar %1$s + + + Buscar inicios de sesión + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rMX/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rMX/strings.xml new file mode 100644 index 0000000000..46eb7b70dd --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es-rMX/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sin nombre de usuario) + + + Contraseña para %1$s + + + Verificación fallida + + + %1$s no pudo verificar la autenticidad de la aplicación. ¿Deseas continuar autocompletando las credenciales seleccionadas? + + + + + + No + + + Buscar %1$s + + + Buscar inicios de sesión + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-es/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..925d071c4a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-es/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sin nombre de usuario) + + + Contraseña para %1$s + + + Ha fallado la verificación + + + %1$s no ha podido verificar la autenticidad de la aplicación. ¿Quieres continuar autocompletando las credenciales seleccionadas? + + + + + + No + + + Buscar %1$s + + + Buscar inicios de sesión + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-et/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-et/strings.xml new file mode 100644 index 0000000000..21429a5487 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-et/strings.xml @@ -0,0 +1,45 @@ + + + + Lukusta %1$s lahti + + + (kasutajanime pole) + + + Konto %1$s parool + + + Verifitseerimine ebaõnnestus + + + %1$s polnud võimalik rakenduse autentsust verifitseerida. Kas soovid jätkata valitud kasutajatunnuste automaatse täitmisega? + + + Jah + + + Ei + + + Otsi %1$sist + + + Otsi kasutajatunnuseid + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-eu/strings.xml new file mode 100644 index 0000000000..0670aa1c98 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-eu/strings.xml @@ -0,0 +1,45 @@ + + + + Desblokeatu %1$s + + + (Erabiltzaile-izenik ez) + + + %1$s kontuaren pasahitza + + + Egiaztapenak huts egin du + + + %1$s(e)k ezin izan du aplikazioaren autentikotasuna egiaztatu. Hautatutako kredentzialak automatikoki bete nahi dituzu halere? + + + Bai + + + Ez + + + Bilatu %1$s + + + Bilatu saio-hasierak + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-fa/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000000..c37a4c2e2a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fa/strings.xml @@ -0,0 +1,45 @@ + + + + بازکردن %1$s + + + (بدون نام‌کاربری) + + + گذرواژه برای %1$s + + + تأیید ناموفق بود + + + %1$s نتوانست هویت برنامه را تایید کند. آیا می‌خواهید به پُر کردن خودکار اطلاعات ادامه دهید؟ + + + بله + + + خیر + + + جست‌وجو در %1$s + + + جست‌وجو در واردشده ها + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ff/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ff/strings.xml new file mode 100644 index 0000000000..db106f3585 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ff/strings.xml @@ -0,0 +1,17 @@ + + + + + (Alaa innde kuutoro) + + + Yiylo %1$s + + + Yiylo ceŋorɗe + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-fi/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000000..58c0fa2414 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fi/strings.xml @@ -0,0 +1,45 @@ + + + + Avaa %1$sin lukitus + + + (ei käyttäjätunnusta) + + + Salasana tilille %1$s + + + Vahvistus epäonnistui + + + %1$s ei voinut vahvistaa sovelluksen aitoutta. Haluatko jatkaa valittujen kirjautumistietoijen automaattista täydentämistä? + + + Kyllä + + + Ei + + + Etsi %1$s + + + Etsi kirjautumistietoja + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-fr/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..240da07fd6 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fr/strings.xml @@ -0,0 +1,45 @@ + + + + Déverrouiller %1$s + + + (Aucun nom d’utilisateur) + + + Mot de passe pour %1$s + + + Échec de la vérification + + + %1$s n’a pas pu vérifier l’authenticité de l’application. Voulez-vous tout de même procéder au remplissage automatique des identifiants sélectionnés ? + + + Oui + + + Non + + + Rechercher dans %1$s + + + Rechercher des identifiants + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-fur/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fur/strings.xml new file mode 100644 index 0000000000..0f2eb2f0d8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fur/strings.xml @@ -0,0 +1,45 @@ + + + + Sbloche %1$s + + + (Nissun non utent) + + + Password par %1$s + + + Verifiche falide + + + %1$s nol à podût verificâ la autenticitât de aplicazion. Procedi cu la compilazion automatiche doprant lis credenziâls selezionadis? + + + + + + No + + + Cîr in %1$s + + + Cîr tes credenziâls + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-fy-rNL/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fy-rNL/strings.xml new file mode 100644 index 0000000000..7056018333 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-fy-rNL/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s ûntskoattelje + + + (Gjin brûkersnamme) + + + Wachtwurd foar %1$s + + + Ferifikaasje mislearre + + + %1$s koe de autentisiteit fan de tapassing net ferifiearje. Wolle jo trochgean mei it automatysk ynfoljen fan de selektearre oanmeldgegevens? + + + Ja + + + Nee + + + %1$s trochsykje + + + Oanmeldingen sykje + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-gd/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-gd/strings.xml new file mode 100644 index 0000000000..ab2a418523 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-gd/strings.xml @@ -0,0 +1,45 @@ + + + + Thoir a’ ghlas far %1$s + + + (Gun ainm-cleachdaiche) + + + Am facal-faire airson %1$s + + + Dh’fhàillig leis an dearbhadh + + + Cha deach le %1$s dearbh-aithneachadh a dhèanamh air a aplacaid. A bheil thu airson leantainn air adhart le lìonadh fèin-obrachail an teisteis a thagh thu? + + + Tha + + + Chan eil + + + Lorg %1$s + + + Lorg sna clàraidhean a-steach + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-gl/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-gl/strings.xml new file mode 100644 index 0000000000..eadc31c59e --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-gl/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sen nome de usuario) + + + Contrasinal de %1$s + + + Fallou a comprobación + + + %1$s non puido verificar a autenticidade da aplicación. Quere continuar co enchido automático das credenciais seleccionadas? + + + Si + + + Non + + + Buscar en %1$s + + + Buscar nas credenciais + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-gn/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-gn/strings.xml new file mode 100644 index 0000000000..47c023866d --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-gn/strings.xml @@ -0,0 +1,45 @@ + + + + Emyandyjey %1$s + + + (Poruhára hera’ỹva) + + + %1$s ñe’ẽñemi + + + Ojavy jehechajey + + + %1$s ndaikatúi ohechajey pe tembiporu’i ha’épa añeteguáva. ¿Emyanyhẽsevépa umi terarenda jeporavopyre? + + + Héẽ + + + Nahániri + + + Eheka %1$s + + + Eheka tembiapo ñepyrũ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-hi-rIN/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hi-rIN/strings.xml new file mode 100644 index 0000000000..df50691a99 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hi-rIN/strings.xml @@ -0,0 +1,38 @@ + + + + + %1$s को खोलें + + + (कोई उपयोगकर्ता नाम नहीं) + + + %1$s के लिए पासवर्ड + + + सत्यापन विफल + + + हां + + + नहीं‌ + + + %1$s खोजें + + + लॉगिन खोजें + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-hil/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hil/strings.xml new file mode 100644 index 0000000000..98eb4327ac --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hil/strings.xml @@ -0,0 +1,17 @@ + + + + + Huo + + + Indi + + + Pangitaon %1$s + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-hr/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000000..cae7d79ac2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hr/strings.xml @@ -0,0 +1,45 @@ + + + + Otključaj %1$s + + + (nema korisničkog imena) + + + Lozinka za %1$s + + + Provjera nije uspjela + + + %1$s nije mogao provjeriti autentičnost aplikacije. Želite li nastaviti s automatskim popunjavanjem odabranih vjerodajnica? + + + Da + + + Ne + + + Pretraži %1$s + + + Pretraži prijave + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-hsb/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hsb/strings.xml new file mode 100644 index 0000000000..2576402485 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hsb/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s hižo njeblokować + + + (Žane wužiwarske mjeno) + + + Hesło za %1$s + + + Přepruwowanje je so nimokuliło + + + %1$s njemóžeše awtentiskosć nałoženja přepruwować. Chceće z awtomatiskim wupjelnjenjom wubranych přizjewjenskich datow pokročować? + + + Haj + + + + + + %1$s přepytać + + + Přizjewjenja pytać + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-hu/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..f71c532838 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hu/strings.xml @@ -0,0 +1,45 @@ + + + + A %1$s feloldása + + + (Nincs felhasználónév) + + + Jelszó a következőhöz: %1$s + + + Az ellenőrzés sikertelen + + + A(z) %1$s nem tudta ellenőrizni az alkalmazás hitelességét. Folytatja a kiválasztott hitelesítő adatok automatikus kitöltését? + + + Igen + + + Nem + + + %1$s keresés + + + Bejelentkezések keresése + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-hy-rAM/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000000..5e194f3fe6 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,45 @@ + + + + Ապակողպեл %1$s-ը + + + (չկա օգտվողի անուն) + + + Գաղտնաբառ %1$s-ի համար + + + Նույնականացումը ձախողվեց + + + %1$s-ը չկարողացավ ստուգել ծրագրի իսկությունը: Ցանկանո՞ւմ եք շարունակել ընտրված հավատարմագրերի ինքնալրացումը: + + + Այո + + + Ոչ + + + Որոնել %1$s-ում + + + Որոնել մուտքանուններ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ia/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ia/strings.xml new file mode 100644 index 0000000000..c7ce79ab63 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ia/strings.xml @@ -0,0 +1,45 @@ + + + + Disblocar %1$s + + + (Nulle nomine de usator) + + + Contrasigno pro %1$s + + + Verification fallite + + + %1$s non pote verificar le authenticitate del application. Vole tu continuar con auto-plenamento del seligite credentiales? + + + Si + + + No + + + Cercar in %1$s + + + Cercar credentiales + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-in/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-in/strings.xml new file mode 100644 index 0000000000..914a06b96f --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-in/strings.xml @@ -0,0 +1,45 @@ + + + + Buka %1$s + + + (Tidak ada nama pengguna) + + + Sandi untuk %1$s + + + Verifikasi gagal + + + %1$s tidak dapat memeriksa keaslian aplikasi tersebut. Ingin melanjutkan dengan mengisi otomatis dengan kredensial terpilih? + + + Ya + + + Tidak + + + Cari %1$s + + + Cari log masuk + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-is/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-is/strings.xml new file mode 100644 index 0000000000..750ea8d274 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-is/strings.xml @@ -0,0 +1,45 @@ + + + + Opna fyrir %1$s + + + (ekkert notandanafn) + + + Lykilorð fyrir %1$s + + + Staðfesting mistókst + + + %1$s gat ekki staðfest áreiðanleika forritsins. Viltu halda áfram að fylla sjálfvirkt út valin auðkenni? + + + + + + Nei + + + Leita í %1$s + + + Leita að innskráningu + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-it/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-it/strings.xml new file mode 100644 index 0000000000..0397938e5f --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-it/strings.xml @@ -0,0 +1,45 @@ + + + + Sblocca %1$s + + + (nessun nome utente) + + + Password per %1$s + + + Verifica non riuscita + + + %1$s non ha potuto verificare l’autenticità dell’applicazione. Procedere con la compilazione automatica usando le credenziali selezionate? + + + + + + No + + + Cerca in %1$s + + + Cerca nelle credenziali + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-iw/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000000..baf67c7f81 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-iw/strings.xml @@ -0,0 +1,45 @@ + + + + שחרור נעילת %1$s + + + (אין שם משתמש) + + + ססמה עבור %1$s + + + האימות נכשל + + + ‏%1$s לא הצליח לאמת את אמינות היישומון. האם ברצונך להמשיך במילוי אוטומטי של פרטי הכניסה שנבחרו? + + + כן + + + לא + + + חיפוש ב־%1$s + + + חיפוש כניסות + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ja/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000000..b3a0989710 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ja/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s のロックを解除 + + + (ユーザー名なし) + + + %1$s のパスワード + + + 検証失敗 + + + %1$s がアプリケーションの信頼性を検証できませんでした。選択した資格情報の自動入力を続けますか? + + + はい + + + いいえ + + + %1$s を検索 + + + ログイン情報を検索 + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ka/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ka/strings.xml new file mode 100644 index 0000000000..fd9b99f9d2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ka/strings.xml @@ -0,0 +1,45 @@ + + + + გაიხსნას %1$s + + + (სახელის გარეშე) + + + პაროლი ანგარიშისთვის %1$s + + + დამოწმება ვერ მოხერხდა + + + %1$s ვერ ადასტურებს აპლიკაციის ნამდვილობას. გსურთ, განაგრძოთ შერჩეული მონაცემებით თვითშევსება? + + + დიახ + + + არა + + + ძიება - %1$s + + + ანგარიშების ძიება + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-kaa/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kaa/strings.xml new file mode 100644 index 0000000000..d601de91b8 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kaa/strings.xml @@ -0,0 +1,46 @@ + + + + + %1$s bloktan shıǵarıw + + + (Paydalanıwshı atı joq) + + + %1$s ushın parol + + + Tastıyıqlaw ámelge aspadı + + + %1$s baǵdarlamanıń haqıyqıylıǵın teksere almadı. Tańlanǵan esap maǵlıwmatların avtomat tárizde toltırıwdı qáleysiz be? + + + Awa + + + Yaq + + + %1$s izleń + + + Loginlerdi zleń + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-kab/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kab/strings.xml new file mode 100644 index 0000000000..45a385c8f1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kab/strings.xml @@ -0,0 +1,45 @@ + + + + Serreḥ i %1$s + + + (Ulac isem n useqdac) + + + Awal uffir i %1$s + + + Asenqed ur yeddi ara + + + %1$s ur ezmir ara ad isenqed asesteb n usnas. Tebɣiḍ ad tkemmleḍ s tacaṛt tawurmant inekcam yettwafernen? + + + Ih + + + Ala + + + Nadi %1$s + + + Nadi inekcam + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-kk/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kk/strings.xml new file mode 100644 index 0000000000..8f71c1997a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kk/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s босату + + + (Пайдаланушы аты жоқ) + + + %1$s үшін пароль + + + Верификация сәтсіз аяқталды + + + %1$s қолданба шынайылығын тексере алмады. Таңдалған тіркелу мәліметтерін автоматты түрде толтырумен жалғастыру керек пе? + + + Иә + + + Жоқ + + + %1$s ішінен іздеу + + + Логиндерден іздеу + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-kmr/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kmr/strings.xml new file mode 100644 index 0000000000..5a91889228 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-kmr/strings.xml @@ -0,0 +1,45 @@ + + + + Kilîda %1$s’ê veke + + + (Navê bikarhêner tune) + + + Pêborîna %1$s’ê + + + Piştrastkirin têk çû + + + %1$s nekariye rayeya serlêdanê piştrast bike. Gelo tu dixwazî zanyariyên hesabê hatî hilbijartin xweber bê dagitin û wisa dewam bikî? + + + Erê + + + Na + + + Li %1$s bigere + + + Li têketinan bigere + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ko/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000000..c8dc9de112 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ko/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s 잠금 해제 + + + (사용자 이름 없음) + + + %1$s 비밀번호 + + + 확인 실패 + + + %1$s가 애플리케이션의 신뢰성을 확인할 수 없습니다. 선택한 자격 증명을 자동으로 채우시겠습니까? + + + + + + 아니요 + + + %1$s 검색 + + + 로그인 검색 + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-lo/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-lo/strings.xml new file mode 100644 index 0000000000..d5b78f234d --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-lo/strings.xml @@ -0,0 +1,45 @@ + + + + ປົດລັອກ %1$s + + + (ບໍ່ມີຊື່ຜູ້ໃຊ້) + + + ລະຫັດຜ່ານສຳລັບ %1$s + + + ການຢັ້ງຢືນລົ້ມເຫລວ + + + %1$s ບໍ່ສາມາດລະບຸຄວາມຖືກຕ້ອງຂອງແອັບພຣິເຄຊັນໄດ້. ທ່ານຕ້ອງການຕື່ມຂໍ້ມູນປະຈຳຕົວຂອງທ່ານທີ່ເລືອກອັດຕະໂນມັດຫລືບໍ? + + + ຕ້ອງການ + + + ບໍ່ຕ້ອງການ + + + ຄົ້ນຫາ %1$s + + + ຄົ້ນຫາຂໍ້ມູນການລັອກອິນ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-lt/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000000..b38c5ee06d --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-lt/strings.xml @@ -0,0 +1,45 @@ + + + + Atrakinti „%1$s“ + + + (nėra naudotojo vardo) + + + Paskyros „%1$s“ slaptažodis + + + Patvirtinimas nepavyko + + + „%1$s“ nepavyko patvirtinti programos autentiškumo. Ar norite automatiškai užpildyti prisijungimo duomenis? + + + Taip + + + Ne + + + Ieškoti „%1$s“ + + + Ieškoti prisijungimų + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-mix/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-mix/strings.xml new file mode 100644 index 0000000000..ada67769d5 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-mix/strings.xml @@ -0,0 +1,21 @@ + + + + Kuna %1$s + + + (Koo sivi) + + + Tu^un se^e %s + + + No + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-my/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-my/strings.xml new file mode 100644 index 0000000000..3beb79aeec --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-my/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s ကို ဖွင့်ပါ + + + (အသုံးပြုသူအမည် မရှိ) + + + %1$s အတွက် စကားဝှက် + + + မှန်ကန်မှုကို စစ်ဆေးအတည်ပြုခြင်း မအောင်မြင်ပါ + + + %1$s သည် အပလီကေးရှင်း ၏ စစ်မှန်မှုကို အတည်မပြုနိုင်ပါ။ သင်ရွေးချယ်ထားသော အထောက်အထားများကို အလိုအလျောက်ဖြည့်ခြင်းနှင့် ဆက်လက် လုပ်ဆောင်လိုပါသလား။ + + + လုပ်ဆောင်ပါမည် + + + မလုပ်ဆောင်တော့ပါ + + + %1$s ကို ရှာပါ + + + လော့ဂ်အင် ဝင်ရောက်မှုများကို ရှာပါ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-nb-rNO/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 0000000000..cd3ef4edbc --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,45 @@ + + + + Lås opp %1$s + + + (Uten brukernavn) + + + Passord for %1$s + + + Bekreftelsen mislyktes + + + %1$s kunne ikke bekrefte ektheten til applikasjonen. Vil du fortsette med autoutfylling av valgte innloggingsinformasjon? + + + Ja + + + Nei + + + Søk %1$s + + + Søk innlogginger + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ne-rNP/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000000..c193be1445 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ne-rNP/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s लाई अनलक गर्नुहोस् + + + (प्रयोगकर्ताको नाम छैन) + + + %1$s को लागि पासवर्ड + + + प्रमाणीकरण असफल भयो + + + %1$s ले यो एपको प्रामाणिकता प्रमाणित गर्न सकेन। के तपाइँ छानिएका प्रमाणहरू स्वत: भरेर अगाडि बढ्न चाहनुहुन्छ? + + + हुन्छ + + + हुँदैन + + + %1$s खोज्नुहोस् + + + लगइनहरु खोज्नुहोस् + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-nl/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000000..b8c7592da0 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-nl/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s ontgrendelen + + + (Geen gebruikersnaam) + + + Wachtwoord voor %1$s + + + Verificatie mislukt + + + %1$s kon de authenticiteit van de toepassing niet verifiëren. Wilt u doorgaan met het automatisch invullen van de geselecteerde aanmeldgegevens? + + + Ja + + + Nee + + + %1$s doorzoeken + + + Aanmeldingen zoeken + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-nn-rNO/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-nn-rNO/strings.xml new file mode 100644 index 0000000000..494754299a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-nn-rNO/strings.xml @@ -0,0 +1,45 @@ + + + + Lås opp %1$s + + + (ikkje noko brukarnamn) + + + Passord for %1$s + + + Mislykka stadfesting + + + %1$s klarte ikkje å stadfeste autentisiteten til programmet. Vil du fortsetje med å automatiskt fylle ut dei valde opplysningane? + + + Ja + + + Nei + + + Søk %1$s + + + Søk innloggingar + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-oc/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-oc/strings.xml new file mode 100644 index 0000000000..13a4bfc6da --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-oc/strings.xml @@ -0,0 +1,45 @@ + + + + Desblocar %1$s + + + (Cap de nom d’utilizaire) + + + Senhal per %1$s + + + Verificacion fracassada + + + %1$s a pas pogut verificar l’autenticitat de l’aplicacion. Volètz contunhar l’autocompletacion dels identificants seleccionats. + + + Òc + + + Non + + + Cercar dins %s + + + Recercar d’identificants + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-or/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-or/strings.xml new file mode 100644 index 0000000000..a189a10c53 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-or/strings.xml @@ -0,0 +1,25 @@ + + + + + (ଉପଭୋକ୍ତାଙ୍କ ନାମ ନାହିଁ) + + + %1$s ପାଇଁ ପାସୱାର୍ଡ଼ + + + ଯାଞ୍ଚ ବିବରଣୀ ବିଫଳ + + + ହଁ + + + ନାଁ + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rIN/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000000..2435dc53f6 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rIN/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s ਅਣ-ਲਾਕ ਕਰੋ + + + (ਕੋਈ ਵਰਤੋਂਕਾਰ ਨਾਂ ਨਹੀਂ) + + + %1$s ਲਈ ਪਾਸਵਰਡ + + + ਤਸਦੀਕ ਅਸਫ਼ਲ ਰਹੀ + + + %1$s ਐਪਲੀਕੇਸ਼ਨ ਦੀ ਪਰਮਾਣਿਕਤਾ ਦੀ ਤਸਦੀਕ ਨਹੀਂ ਕਰ ਸਕਿਆ। ਕੀ ਤੁਸੀਂ ਚੁਣੀਆਂ ਸਨਦਾਂ ਨੂੰ ਆਪੇ-ਭਰਨ ਨਾਲ ਜਾਰੀ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹੋ? + + + ਹਾਂ + + + ਨਹੀਂ + + + %1$s ਖੋਜੋ + + + ਲਾਗਇਨ ਖੋਜੋ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rPK/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rPK/strings.xml new file mode 100644 index 0000000000..ac43539016 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pa-rPK/strings.xml @@ -0,0 +1,46 @@ + + + + + %1$s بند کرن نوں الٹاؤ + + + (کوئی ورتنوالے دا ناں نہیں جاݨیا) + + + %1$s ناں نال پاس‌ورڈ + + + تصدیق نال غلطی ہو گئی اے + + + %1$s ایپ دی تصدیق نہیں کر سکدا۔ کیہ تسیں چݨیاں سنداں نوں آپے بھرن نال جاری رکھݨا چہندے او؟ + + + ہاں + + + نہیں + + + %1$s ایپ چ کھوجو + + + کھاتے چ کھوجو + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-pl/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000000..2b778b31b1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pl/strings.xml @@ -0,0 +1,45 @@ + + + + Odblokuj program %1$s + + + (Bez nazwy użytkownika) + + + Hasło dla konta %1$s + + + Weryfikacja się nie powiodła + + + %1$s nie może zweryfikować autentyczności aplikacji. Czy kontynuować automatyczne wypełnianie wybranych danych logowania? + + + Tak + + + Nie + + + Szukaj w programie %1$s + + + Szukaj danych logowania + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rBR/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..f6b798fd2a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (sem nome de usuário) + + + Senha de %1$s + + + Falha na verificação + + + O %1$s não conseguiu verificar a autenticidade do aplicativo. Quer prosseguir com o preenchimento automático das credenciais selecionadas? + + + Sim + + + Não + + + Procurar no %1$s + + + Procurar contas + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rPT/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..05a46a791d --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,45 @@ + + + + Desbloquear %1$s + + + (Sem nome de utilizador) + + + Palavra-passe para %1$s + + + A confirmação falhou + + + %1$s não conseguiu confirmar a autenticidade da aplicação. Quer continuar com o preenchimento automático das credenciais selecionadas? + + + Sim + + + Não + + + Pesquisar no %1$s + + + Pesquisar credenciais + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-rm/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-rm/strings.xml new file mode 100644 index 0000000000..78619908c2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-rm/strings.xml @@ -0,0 +1,45 @@ + + + + Debloccar %1$s + + + (Nagin num d\'utilisader) + + + Pled-clav per %1$s + + + Verificaziun betg reussida + + + %1$s n\'ha betg pudì verifitgar l\'autenticitad da l\'applicaziun. Vuls ti cuntinuar e laschar emplenir automaticamain las datas d\'annunzia tschernidas? + + + Gea + + + Na + + + Tschertgar en %1$s + + + Retschertgar las datas d\'annunzia + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ro/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000000..cac24165e5 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ro/strings.xml @@ -0,0 +1,14 @@ + + + + + (Niciun nume de utilizator) + + + Caută în %1$s + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ru/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..e92c72cac6 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ru/strings.xml @@ -0,0 +1,45 @@ + + + + Разблокировать %1$s + + + (Нет имени пользователя) + + + Пароль для %1$s + + + Проверка не удалась + + + %1$s не удалось проверить подлинность приложения. Вы хотите произвести автозаполнение выбранных учётных данных? + + + Да + + + Нет + + + Искать в %1$s + + + Поиск логинов + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sat/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sat/strings.xml new file mode 100644 index 0000000000..70fecdcbca --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sat/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ + + + (ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱧᱩᱛᱩᱢ ᱵᱟᱹᱱᱩᱜᱼᱟ) + + + %1$s ᱞᱟᱹᱜᱤᱫ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ + + + ᱯᱨᱚᱢᱟᱬᱤᱛ ᱰᱤᱜᱟᱹᱣ ᱮᱱᱟ + + + %1$s ᱫᱚ ᱮᱯᱞᱤᱥᱠᱮᱥᱚᱱ ᱨᱮᱭᱟᱜ ᱥᱟᱹᱨᱤ ᱠᱚ ᱵᱟᱭ ᱯᱩᱥᱴᱟᱹᱣ ᱫᱟᱲᱮᱭᱟᱫᱟᱭ ᱾ ᱟᱢ ᱪᱮᱫ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱ ᱠᱨᱮᱰᱮᱱᱥᱤᱭᱟᱞᱥ ᱛᱮ ᱟᱡ ᱛᱮ ᱯᱩᱨᱟᱹᱣ ᱪᱷᱚᱣᱟᱜ ᱥᱮᱱᱟᱢ ᱠᱟᱱᱟ ᱥᱮ? + + + ᱦᱮᱸ + + + ᱵᱟᱝ + + + %s ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ + + + ᱞᱚᱜᱤᱱ ᱠᱚ ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sc/strings.xml new file mode 100644 index 0000000000..e05c371bed --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sc/strings.xml @@ -0,0 +1,37 @@ + + + + Isbloca %1$s + + + (Nissunu nòmine utente) + + + Crae pro %1$s + + + Faddina in sa verìfica + + + Eja + + + Nono + + + Chirca %1$s + + + Chirca credentziales + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-si/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-si/strings.xml new file mode 100644 index 0000000000..74b25af4f2 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-si/strings.xml @@ -0,0 +1,46 @@ + + + + + %1$s අගුළු හරින්න + + + (පරිශීලක නාමය නැත) + + + %1$s සඳහා මුරපදය + + + සත්‍යාපනයට අසමත් විය! + + + %1$s සඳහා යෙදුම සත්‍යාපනයට නොහැකි විය. ඔබට තෝරාගත් අක්තපත්‍ර ස්වයංක්‍රීයව පිරවීමෙන් ඉදිරියට යාමට අවශ්‍යද? + + + ඔව් + + + නැහැ + + + %1$s සොයන්න + + + පිවිසුම් සොයන්න + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sk/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000000..4bc86cbb62 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sk/strings.xml @@ -0,0 +1,45 @@ + + + + Odomknúť %1$s + + + (Žiadne používateľské meno) + + + Heslo pre účet %1$s + + + Overenie zlyhalo + + + Prehliadač %1$s nemohol overiť pravosť aplikácie. Chcete pokračovať v automatickom dopĺňaní prihlasovacích údajov? + + + Áno + + + Nie + + + Hľadať v aplikácii %1$s + + + Hľadať v prihlasovacích údajoch + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-skr/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-skr/strings.xml new file mode 100644 index 0000000000..378959c47c --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-skr/strings.xml @@ -0,0 +1,46 @@ + + + + + %1$s اݨ لاک کرو + + + (ورتݨ ناں کوئی کائنی) + + + %1$s کیتے پاس ورڈ + + + پڑتال ناکام تھی ڳئی + + + %1$s ایپ دے مستند ہووݨ دی تصدیق کائنی کر سڳا۔ بھلا تساں چݨی اسناد کوں خودکاربھرݨ نال اڳوں تے ودھݨ چاہسو؟ + + + جیا + + + کو + + + %1$s ڳولو + + + لاگ ان ڳولو + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sl/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000000..e31de3c7ff --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sl/strings.xml @@ -0,0 +1,45 @@ + + + + Odkleni %1$s + + + (brez uporabniškega imena) + + + Geslo za %1$s + + + Preverjanje ni uspelo + + + %1$s ni mogel preveriti pristnosti aplikacije. Ali želite nadaljevati s samodejnim izpolnjevanjem izbranih podatkov za prijavo? + + + Da + + + Ne + + + Išči v %1$su + + + Iskanje prijav + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sq/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sq/strings.xml new file mode 100644 index 0000000000..ae824fa834 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sq/strings.xml @@ -0,0 +1,45 @@ + + + + Shkyçe %1$s + + + (Pa emër përdoruesi) + + + Fjalëkalim për %1$s + + + Verifikimi dështoi + + + %1$s s’verifikoi dot mirëfilltësinë e këtij aplikacioni. Doni të kryhet vetëplotësimi i kredencialeve të përzgjedhura? + + + Po + + + Jo + + + Kërko për %1$s + + + Kërkoni te kredenciale hyrjesh + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sr/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000000..cb3c56e1ed --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sr/strings.xml @@ -0,0 +1,45 @@ + + + + Откључај %1$s + + + (без корисничког имена) + + + Лозинка за %1$s + + + Провера није успела + + + %1$s није могао да провери аутентичност апликације. Желите ли да наставите с аутоматским попуњавањем изабраних акредитива? + + + Да + + + Не + + + Претражи %1$s + + + Претражи пријаве + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-su/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-su/strings.xml new file mode 100644 index 0000000000..094162d8a3 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-su/strings.xml @@ -0,0 +1,45 @@ + + + + Buka konci %1$s + + + (Taya sandiasma) + + + Kecap sandi pikeun %1$s + + + Péripikasi gagal + + + %1$s teu bisa muguhkeun oténtisitas aplikasina. Rék diteruskeun ku ngeusi otomatis data nu dipilih? + + + Enya + + + Moal + + + Paluruh %1$s + + + Paluruh login + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-sv-rSE/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sv-rSE/strings.xml new file mode 100644 index 0000000000..c35fb6ef1c --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-sv-rSE/strings.xml @@ -0,0 +1,45 @@ + + + + Lås upp %1$s + + + (Inget användarnamn) + + + Lösenord för %1$s + + + Verifieringen misslyckades + + + %1$s kunde inte verifiera programmets äkthet. Vill du fortsätta med att automatiskt fylla i de valda uppgifterna? + + + Ja + + + Nej + + + Sök efter %1$s + + + Sök inloggningar + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ta/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ta/strings.xml new file mode 100644 index 0000000000..0b6ad1ab00 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ta/strings.xml @@ -0,0 +1,41 @@ + + + + + %1$s ஐ பூட்டவிழ் + + + (பயனர்பெயர் இல்லை) + + + %1$s க்கான கடவுச்சொல் + + + சரிபார்ப்பு தோல்வி + + + %1$s ஆல் செயலியின் நம்பகத்தன்மையை சரிபார்க்க இயலவில்லை. தேர்ந்தெடுக்கப்பட்ட நற்சான்றிதழ்களை தானாக நிரப்புவதன் மூலம் தொடர விரும்புகிறீர்களா? + + + ஆம் + + + இல்லை + + + புகுபதிகைகளைத் தேடு + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-te/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-te/strings.xml new file mode 100644 index 0000000000..79069b6350 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-te/strings.xml @@ -0,0 +1,25 @@ + + + + + (వాడుకరి పేరు లేదు) + + + %1$sకి సంకేతపదం + + + తనిఖీ విఫలమైంది + + + అవును + + + వద్దు + + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-tg/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tg/strings.xml new file mode 100644 index 0000000000..a454579cb1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tg/strings.xml @@ -0,0 +1,45 @@ + + + + Кушодани қулфи %1$s + + + (Номи корбар нест) + + + Ниҳонвожа барои %1$s + + + Санҷиши ҳаққоният иҷро нашуд + + + %1$s ҳаққонияти барномаро тасдиқ карда натавонист. Шумо мехоҳед, ки маълумоти воридшавии интихобшударо ба таври худкор пур карда, идома диҳед? + + + Ҳа + + + Не + + + Ҷустуҷӯ дар %1$s + + + Ҷустуҷӯи воридшавиҳо + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-th/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..251d185372 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-th/strings.xml @@ -0,0 +1,45 @@ + + + + ปลดล็อค %1$s + + + (ไม่มีชื่อผู้ใช้) + + + รหัสผ่านสำหรับ %1$s + + + การตรวจสอบล้มเหลว + + + %1$s ไม่สามารถตรวจสอบความถูกต้องของแอปพลิเคชันได้ คุณต้องการเติมข้อมูลประจำตัวที่เลือกอัตโนมัติต่อหรือไม่? + + + ใช่ + + + ไม่ + + + ค้นหา %1$s + + + ค้นหาการเข้าสู่ระบบ + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-tl/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tl/strings.xml new file mode 100644 index 0000000000..792e13cf2e --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tl/strings.xml @@ -0,0 +1,45 @@ + + + + i-Unlock ang %1$s + + + (No username) + + + Password para sa %1$s + + + Nabigo ang pag-verify + + + Hindi ma-verify ng %1$s ang pagiging tunay ng application. Nais mo bang magpatuloy sa pag-autofill ng mga napiling kredensyal? + + + Oo + + + Hindi + + + Hanapin sa %1$s + + + Hanapin sa mga login + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-tok/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tok/strings.xml new file mode 100644 index 0000000000..a370d24b26 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tok/strings.xml @@ -0,0 +1,45 @@ + + + + o open e %1$s + + + (nimi li lon ala) + + + nimi open tawa ni: %1$s + + + mi ken ala pona e ilo + + + ilo %1$s li ken ala pona e ilo. sina pana ala pana e nimi sina? + + + pana + + + ala + + + o lukin e %1$s + + + o lukin e nimi open + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-tr/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000000..5292e1edbb --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tr/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s kilidini aç + + + (Kullanıcı adı yok) + + + %1$s parolası + + + Doğrulama başarısız + + + %1$s, uygulamanın yetkinliğini doğrulayamadı. Seçili hesap bilgilerini otomatik olarak doldurmaya devam etmek istiyor musunuz? + + + Evet + + + Hayır + + + %1$s’ta ara + + + Hesaplarda ara + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-trs/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-trs/strings.xml new file mode 100644 index 0000000000..8d080ad617 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-trs/strings.xml @@ -0,0 +1,45 @@ + + + + Dūgi\'iaj sun\' %1$s + + + (Nitāj si yūgui usuario hua) + + + Da\’nga\’ huì guendâ %1$s + + + Nu gā’ue nātsij man + + + Nu gā’hue nātsij %1$s si huā hue’ê aplikasiûn nan. Ruhuât gān’ānjt ne’ ñāa da’ gīsìj nej kredenciâ gida’a raj. + + + Ga\'ue + + + Si ga\'ue + + + Nanà\'huì\' %1$s + + + Nānà\'uì\' nej riña gayi\'ît sēsiûn + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-tt/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tt/strings.xml new file mode 100644 index 0000000000..5f37bea3a0 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tt/strings.xml @@ -0,0 +1,37 @@ + + + + %1$s программасын ачу + + + (Кулланучы исеме юк) + + + %1$s өчен парол + + + Раслау уңышсыз тәмамланды + + + Әйе + + + Юк + + + %1$s эченнән эзләү + + + Логиннардан эзләү + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-tzm/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tzm/strings.xml new file mode 100644 index 0000000000..a83b882497 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-tzm/strings.xml @@ -0,0 +1,34 @@ + + + + + Rẓem %1$s + + + (Walu yism unessemres) + + + Taguri n uzerray i %1$s + + + Yah + + + Uhu + + + Rzu %1$s + + + Rzu inekcam + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ug/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ug/strings.xml new file mode 100644 index 0000000000..129dec6052 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ug/strings.xml @@ -0,0 +1,46 @@ + + + + + %1$s قۇلۇپىنى ئاچ + + + (ئىشلەتكۈچى ئىسمى يوق) + + + %1$s ئىمى + + + دەلىللىيەلمىدى + + + %1$s ئەپنىڭ چىنلىقىنى دەلىللىيەلمىدى. ئۆزلۈكىدىن تولدۇرۇلۇپ تاللانغان تىزىمغا كىرىش ئۇچۇرىنى داۋاملاشتۇرامسىز؟ + + + ھەئە + + + ياق + + + %1$sدىن ئىزدەش + + + كىرىش خاتىرىسىنى ئىزدەش + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-uk/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000000..894bbb4520 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-uk/strings.xml @@ -0,0 +1,45 @@ + + + + Розблокувати %1$s + + + (Без імені користувача) + + + Пароль для %1$s + + + Помилка перевірки + + + %1$s не вдалося перевірити справжність програми. Продовжити автозаповнення вибраних облікових даних? + + + Так + + + Ні + + + Пошук в %1$s + + + Шукати паролі + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-ur/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ur/strings.xml new file mode 100644 index 0000000000..e5eccda540 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-ur/strings.xml @@ -0,0 +1,37 @@ + + + + %1$s ان لاک کریں + + + (صارف نام کا نہیں) + + + %1$sکے لئے پاسورڈ + + + توثیق کاری ناکام ہوگئی + + + جی ہاں + + + جی نہیں + + + %1$s تلاش کریں + + + لاگ ان تلاش کریں + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-uz/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-uz/strings.xml new file mode 100644 index 0000000000..27abed151a --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-uz/strings.xml @@ -0,0 +1,45 @@ + + + + %1$s qulfini ochish + + + (Foydalanuvchi nomi yoʻq) + + + %1$s uchun parol + + + Tasdiqlanmadi + + + %1$s ilovaning haqiqiyligini tekshira olmadi. Tanlangan hisob maʼlumotlarini avtomatik toʻldirishni davom ettirmoqchimisiz? + + + Ha + + + Yoʻq + + + %1$sni qidirish + + + Loginlarni qidirish + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-vec/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-vec/strings.xml new file mode 100644 index 0000000000..410e0a27bd --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-vec/strings.xml @@ -0,0 +1,45 @@ + + + + Sbloca %1$s + + + (Nisun nòme utente) + + + Password par %1$s + + + Verifega no riusìa + + + %1$s non el ga posudo verifegare l’autentisidà de l’aplicasion. Prosedare con ƚa conpilasion otomatega uxando ƚe credensiaƚi selesionà? + + + + + + + + + Cata en %1$s + + + Cata enteƚe credensiaƚi + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-vi/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000000..c474dc0979 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-vi/strings.xml @@ -0,0 +1,45 @@ + + + + Mở khóa %1$s + + + (Không có tên người dùng) + + + Mật khẩu cho %1$s + + + Xác minh thất bại + + + %1$s không thể xác minh tính xác thực của ứng dụng. Bạn có muốn tiếp tục tự động điền thông tin đăng nhập đã chọn không? + + + + + + Không + + + Tìm kiếm %1$s + + + Tìm kiếm thông tin đăng nhập + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-yo/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-yo/strings.xml new file mode 100644 index 0000000000..283990d696 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-yo/strings.xml @@ -0,0 +1,46 @@ + + + + Ṣi sílẹ̀ %1$s + + + (Kò sí orúkọ àmúlò) + + + + Pásíwọọ̀dù fún %1$s + + + Iṣẹ́ ìmúdájú kùnà + + + %1$s kò le rí àrídájú fún áàpù náà. Ṣé o fẹ́ tẹ̀síwájú pẹ̀lú yíyàn-aládàáṣe fún àwọn ìwé-ẹ̀rí náà? + + + Bẹ́ẹ̀ni + + + Bẹ́ẹ̀ kọ́ + + + Ṣàwárí %1$s + + + Yẹ àwọn ìwọlé wò + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rCN/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..5a15f62f28 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,45 @@ + + + + 解锁 %1$s + + + (无用户名) + + + %1$s 的密码 + + + 验证失败 + + + %1$s 无法验证此应用程序的真实性,您确定要自动填充选择的登录信息吗? + + + + + + + + + 搜索保存于 %1$s 的登录信息 + + + 搜索登录信息 + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rTW/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..4c91ff38bc --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,45 @@ + + + + 解鎖 %1$s + + + (無使用者名稱) + + + %1$s 的密碼 + + + 驗證失敗 + + + %1$s 無法驗證此應用程式的真實性,您確定要自動填入選擇的登入資訊嗎? + + + 要填入 + + + 不要填入 + + + 搜尋儲存於 %1$s 的登入資訊 + + + 搜尋登入資訊 + diff --git a/mobile/android/android-components/components/feature/autofill/src/main/res/values/strings.xml b/mobile/android/android-components/components/feature/autofill/src/main/res/values/strings.xml new file mode 100644 index 0000000000..1445f98965 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/main/res/values/strings.xml @@ -0,0 +1,48 @@ + + + + + Unlock %1$s + + + (No username) + + + Password for %1$s + + + Verification failed + + + %1$s could not verify the authenticity of the application. Do you want to proceed with autofilling the selected credentials? + + + Yes + + + No + + + Search %1$s + + + Search logins + diff --git a/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/AutofillUseCasesTest.kt b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/AutofillUseCasesTest.kt new file mode 100644 index 0000000000..21a96c89b9 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/AutofillUseCasesTest.kt @@ -0,0 +1,192 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill + +import android.content.Context +import android.view.autofill.AutofillManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.support.test.any +import mozilla.components.support.test.mock +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class AutofillUseCasesTest { + @Test + fun testIsSupported() { + val context: Context = mock() + val autofillManager: AutofillManager = mock() + doReturn(autofillManager).`when`(context).getSystemService(AutofillManager::class.java) + doReturn(true).`when`(autofillManager).isAutofillSupported + + assertFalse(AutofillUseCases(sdkVersion = 21).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 22).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 23).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 24).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 25).isSupported(context)) + + assertTrue(AutofillUseCases(sdkVersion = 26).isSupported(context)) + assertTrue(AutofillUseCases(sdkVersion = 27).isSupported(context)) + assertTrue(AutofillUseCases(sdkVersion = 28).isSupported(context)) + assertTrue(AutofillUseCases(sdkVersion = 29).isSupported(context)) + assertTrue(AutofillUseCases(sdkVersion = 30).isSupported(context)) + } + + @Test + fun testIsNotSupported() { + val context: Context = mock() + val autofillManager: AutofillManager = mock() + doReturn(autofillManager).`when`(context).getSystemService(AutofillManager::class.java) + doReturn(false).`when`(autofillManager).isAutofillSupported + + assertFalse(AutofillUseCases(sdkVersion = 21).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 22).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 23).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 24).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 25).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 26).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 27).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 28).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 29).isSupported(context)) + assertFalse(AutofillUseCases(sdkVersion = 30).isSupported(context)) + } + + @Test + fun testIsEnabled() { + val context: Context = mock() + val autofillManager: AutofillManager = mock() + doReturn(autofillManager).`when`(context).getSystemService(AutofillManager::class.java) + doReturn(true).`when`(autofillManager).hasEnabledAutofillServices() + + assertFalse(AutofillUseCases(sdkVersion = 21).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 22).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 23).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 24).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 25).isEnabled(context)) + + assertTrue(AutofillUseCases(sdkVersion = 26).isEnabled(context)) + assertTrue(AutofillUseCases(sdkVersion = 27).isEnabled(context)) + assertTrue(AutofillUseCases(sdkVersion = 28).isEnabled(context)) + assertTrue(AutofillUseCases(sdkVersion = 29).isEnabled(context)) + assertTrue(AutofillUseCases(sdkVersion = 30).isEnabled(context)) + } + + @Test + fun testIsNotEnabled() { + val context: Context = mock() + val autofillManager: AutofillManager = mock() + doReturn(autofillManager).`when`(context).getSystemService(AutofillManager::class.java) + doReturn(false).`when`(autofillManager).hasEnabledAutofillServices() + + assertFalse(AutofillUseCases(sdkVersion = 21).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 22).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 23).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 24).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 25).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 26).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 27).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 28).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 29).isEnabled(context)) + assertFalse(AutofillUseCases(sdkVersion = 30).isEnabled(context)) + } + + @Test + fun testEnable() { + val context: Context = mock() + + AutofillUseCases(sdkVersion = 21).enable(context) + verify(context, never()).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 22).enable(context) + verify(context, never()).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 23).enable(context) + verify(context, never()).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 24).enable(context) + verify(context, never()).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 25).enable(context) + verify(context, never()).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 26).enable(context) + verify(context).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 27).enable(context) + verify(context).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 28).enable(context) + verify(context).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 29).enable(context) + verify(context).startActivity(any()) + reset(context) + + AutofillUseCases(sdkVersion = 30).enable(context) + verify(context).startActivity(any()) + reset(context) + } + + @Test + fun testDisable() { + val context: Context = mock() + val autofillManager: AutofillManager = mock() + doReturn(autofillManager).`when`(context).getSystemService(AutofillManager::class.java) + + AutofillUseCases(sdkVersion = 21).disable(context) + verify(autofillManager, never()).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 22).disable(context) + verify(autofillManager, never()).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 23).disable(context) + verify(autofillManager, never()).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 24).disable(context) + verify(autofillManager, never()).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 25).disable(context) + verify(autofillManager, never()).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 26).disable(context) + verify(autofillManager).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 27).disable(context) + verify(autofillManager).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 28).disable(context) + verify(autofillManager).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 29).disable(context) + verify(autofillManager).disableAutofillServices() + reset(autofillManager) + + AutofillUseCases(sdkVersion = 30).disable(context) + verify(autofillManager).disableAutofillServices() + reset(autofillManager) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt new file mode 100644 index 0000000000..32e825b931 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/handler/FillRequestHandlerTest.kt @@ -0,0 +1,235 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.handler + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import mozilla.components.concept.storage.Login +import mozilla.components.concept.storage.LoginsStorage +import mozilla.components.feature.autofill.AutofillConfiguration +import mozilla.components.feature.autofill.facts.AutofillFacts +import mozilla.components.feature.autofill.response.fill.FillResponseBuilder +import mozilla.components.feature.autofill.response.fill.LoginFillResponseBuilder +import mozilla.components.feature.autofill.test.createMockStructure +import mozilla.components.feature.autofill.ui.AbstractAutofillConfirmActivity +import mozilla.components.feature.autofill.ui.AbstractAutofillSearchActivity +import mozilla.components.feature.autofill.ui.AbstractAutofillUnlockActivity +import mozilla.components.feature.autofill.verify.CredentialAccessVerifier +import mozilla.components.lib.publicsuffixlist.PublicSuffixList +import mozilla.components.support.base.Component +import mozilla.components.support.base.facts.Action +import mozilla.components.support.base.facts.processor.CollectionProcessor +import mozilla.components.support.test.any +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.`when` +import org.robolectric.RobolectricTestRunner +import java.util.UUID + +@ExperimentalCoroutinesApi // for createTestCase +@RunWith(RobolectricTestRunner::class) +internal class FillRequestHandlerTest { + @Test + fun `App - Twitter - With credentials`() { + CollectionProcessor.withFactCollection { facts -> + val credentials = generateRandomLoginFor("twitter.com") + + createTestCase( + filename = "fixtures/app_twitter.xml", + packageName = "com.twitter.android", + logins = mapOf(credentials), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(1, builder.logins.size) + assertEquals(credentials.second, builder.logins[0]) + assertEquals(false, builder.needsConfirmation) + }, + ) + + assertEquals(1, facts.size) + facts[0].apply { + assertEquals(Component.FEATURE_AUTOFILL, component) + assertEquals(Action.SYSTEM, action) + assertEquals(AutofillFacts.Items.AUTOFILL_REQUEST, item) + assertEquals(2, metadata?.size) + assertEquals(true, metadata?.get(AutofillFacts.Metadata.HAS_MATCHING_LOGINS)) + assertEquals(false, metadata?.get(AutofillFacts.Metadata.NEEDS_CONFIRMATION)) + } + } + } + + @Test + fun `App - Twitter - Without credentials`() { + CollectionProcessor.withFactCollection { facts -> + createTestCase( + filename = "fixtures/app_twitter.xml", + packageName = "com.twitter.android", + logins = emptyMap(), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(0, builder.logins.size) + assertEquals(false, builder.needsConfirmation) + }, + ) + + assertEquals(1, facts.size) + facts[0].apply { + assertEquals(Component.FEATURE_AUTOFILL, component) + assertEquals(Action.SYSTEM, action) + assertEquals(AutofillFacts.Items.AUTOFILL_REQUEST, item) + assertEquals(2, metadata?.size) + assertEquals(false, metadata?.get(AutofillFacts.Metadata.HAS_MATCHING_LOGINS)) + assertEquals(false, metadata?.get(AutofillFacts.Metadata.NEEDS_CONFIRMATION)) + } + } + } + + @Test + fun `App - Expensify`() { + createTestCase( + filename = "fixtures/app_expensify.xml", + packageName = "org.me.mobiexpensifyg", + logins = mapOf( + generateRandomLoginFor("expensify.com"), + ), + assertThat = { builder -> + // Unfortunately we are not able to link the app and the website yet. + assertNull(builder) + }, + ) + } + + @Test + fun `App - Facebook`() { + val credentials = generateRandomLoginFor("facebook.com") + createTestCase( + filename = "fixtures/app_facebook.xml", + packageName = "com.facebook.katana", + logins = mapOf(credentials), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(1, builder.logins.size) + assertEquals(credentials.second, builder.logins[0]) + }, + ) + } + + @Test + fun `App - Facebook Lite`() { + val credentials = generateRandomLoginFor("facebook.com") + createTestCase( + filename = "fixtures/app_facebook_lite.xml", + packageName = "com.facebook.lite", + logins = mapOf(credentials), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(1, builder.logins.size) + assertEquals(credentials.second, builder.logins[0]) + }, + ) + } + + @Test + fun `App - Messenger Lite`() { + val credentials = generateRandomLoginFor("facebook.com") + createTestCase( + filename = "fixtures/app_messenger_lite.xml", + packageName = "com.facebook.mlite", + logins = mapOf(credentials), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(1, builder.logins.size) + assertEquals(credentials.second, builder.logins[0]) + }, + ) + } + + @Test + fun `Browser - Fenix Nightly - amazon-co-uk`() { + val credentials = generateRandomLoginFor("amazon.co.uk") + createTestCase( + filename = "fixtures/browser_fenix_amazon.co.uk.xml", + packageName = "org.mozilla.fenix", + logins = mapOf(credentials), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(1, builder.logins.size) + assertEquals(credentials.second, builder.logins[0]) + }, + ) + } + + @Test + fun `Browser - WebView - gmail`() { + val credentials = generateRandomLoginFor("accounts.google.com") + createTestCase( + filename = "fixtures/browser_webview_gmail.xml", + packageName = "org.chromium.webview_shell", + logins = mapOf(credentials), + assertThat = { builder -> + assertNotNull(builder!!) + assertEquals(0, builder.logins.size) + assertEquals(false, builder.needsConfirmation) + }, + ) + } +} + +@ExperimentalCoroutinesApi +private fun FillRequestHandlerTest.createTestCase( + filename: String, + packageName: String, + logins: Map, + assertThat: (B?) -> Unit, + canVerifyRelationship: Boolean = true, +) = runTest { + val structure = createMockStructure(filename, packageName) + + val storage: LoginsStorage = mock() + `when`(storage.getByBaseDomain(anyString())).thenAnswer { invocation -> + val origin = invocation.getArgument(0) as String + println("MockStorage: Query password for $origin") + logins[origin]?.let { listOf(it) } ?: emptyList() + } + + val verifier: CredentialAccessVerifier = mock() + doReturn(canVerifyRelationship).`when`(verifier).hasCredentialRelationship(any(), any(), any()) + + val configuration = AutofillConfiguration( + storage = storage, + publicSuffixList = PublicSuffixList(testContext), + unlockActivity = AbstractAutofillUnlockActivity::class.java, + confirmActivity = AbstractAutofillConfirmActivity::class.java, + searchActivity = AbstractAutofillSearchActivity::class.java, + applicationName = "Test", + httpClient = mock(), + verifier = verifier, + ) + + val handler = FillRequestHandler( + testContext, + configuration, + ) + + val builder = handler.handle(structure) + @Suppress("UNCHECKED_CAST") + assertThat(builder as? B) +} + +private fun generateRandomLoginFor(origin: String): Pair { + return origin to Login( + guid = UUID.randomUUID().toString(), + origin = origin, + username = "user" + UUID.randomUUID().toString(), + password = "password" + UUID.randomUUID().toString(), + ) +} diff --git a/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt new file mode 100644 index 0000000000..c42381e5f9 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/structure/ParsedStructureTest.kt @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.structure + +import android.os.Parcel +import android.view.autofill.AutofillId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ParsedStructureTest { + @Test + fun `Given a ParsedStructure WHEN parcelling and unparcelling it THEN get the same object`() { + // AutofillId constructor is private but it can be constructed from a parcel. + // Use this route instead of mocking to avoid errors like below: + // org.robolectric.shadows.ShadowParcel$UnreliableBehaviorError: Looking for Integer at position 72, found String + val usernameIdAutofillIdParcel = Parcel.obtain().apply { + writeInt(1) // viewId + writeInt(3) // flags + writeInt(78) // virtualIntId + setDataPosition(0) // be a good citizen + } + val passwordIdAutofillParcel = Parcel.obtain().apply { + writeInt(11) // viewId + writeInt(31) // flags + writeInt(781) // virtualIntId + setDataPosition(0) // be a good citizen + } + val parsedStructure = ParsedStructure( + usernameId = AutofillId.CREATOR.createFromParcel(usernameIdAutofillIdParcel), + passwordId = AutofillId.CREATOR.createFromParcel(passwordIdAutofillParcel), + packageName = "test", + webDomain = "https://mozilla.org", + ) + + // Write the object in a new Parcel. + val parcel = Parcel.obtain() + parsedStructure.writeToParcel(parcel, 0) + + // Reset Parcel r/w position to be read from beginning afterwards. + parcel.setDataPosition(0) + + // Reconstruct the original object from the Parcel. + val result = ParsedStructure(parcel) + + assertEquals(parsedStructure, result) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/DOMNavigator.kt b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/DOMNavigator.kt new file mode 100644 index 0000000000..86f8517850 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/DOMNavigator.kt @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.test + +import android.view.autofill.AutofillId +import mozilla.components.feature.autofill.structure.AutofillNodeNavigator +import mozilla.components.feature.autofill.structure.ParsedStructure +import mozilla.components.support.test.mock +import org.w3c.dom.Document +import org.w3c.dom.Element +import java.io.File +import java.util.WeakHashMap +import javax.xml.parsers.DocumentBuilderFactory + +/** + * Alternative [AutofillNodeNavigator] implementation for reading a captured view structure from an + * XML file for testing purposes. + */ +internal class DOMNavigator( + file: File, + override val activityPackageName: String, +) : AutofillNodeNavigator { + + override fun currentText(node: Element): String? { + return node.getAttribute("autofillValue") + } + + private val document: Document + + init { + val db = DocumentBuilderFactory.newInstance().newDocumentBuilder() + file.inputStream().use { + document = db.parse(it) + } + } + + override val rootNodes: List + get() = listOf(document.documentElement) + + override fun childNodes(node: Element): List { + val children = node.childNodes + return (0 until children.length) + .map { children.item(it) } + .filter { it is Element } + .map { it as Element } + } + + override fun clues(node: Element): Iterable { + val attributes = node.attributes + return (0 until attributes.length) + .map { attributes.item(it) } + .mapNotNull { if (it.nodeName != "hint") it.nodeValue else null } + } + + override fun autofillId(node: Element): AutofillId? { + val rawId = if (isEditText(node) || isHtmlInputField(node)) { + attr(node, "autofillId") ?: clues(node).joinToString("|") + } else { + null + } + + return rawId?.let { getOrCreateAutofillIdMock(it) } + } + + override fun isEditText(node: Element): Boolean = + tagName(node) == "EditText" || (inputType(node) and AutofillNodeNavigator.editTextMask) > 0 + + override fun isHtmlInputField(node: Element) = tagName(node) == "input" + + private fun tagName(node: Element) = node.tagName + + override fun isHtmlForm(node: Element): Boolean = node.tagName == "form" + + fun attr(node: Element, name: String) = node.attributes.getNamedItem(name)?.nodeValue + + override fun isFocused(node: Element) = attr(node, "focus") == "true" + + override fun isVisible(node: Element) = attr(node, "visibility")?.let { it == "0" } ?: true + + override fun packageName(node: Element) = attr(node, "idPackage") + + override fun webDomain(node: Element) = attr(node, "webDomain") + + override fun isButton(node: Element): Boolean { + when (node.tagName) { + "Button" -> return true + "button" -> return true + } + + return when (attr(node, "type")) { + "submit" -> true + "button" -> true + else -> false + } + } + + override fun inputType(node: Element): Int = + attr(node, "inputType")?.let { + Integer.parseInt(it, 16) + } ?: 0 + + override fun build( + usernameId: AutofillId?, + passwordId: AutofillId?, + webDomain: String?, + packageName: String, + ): ParsedStructure { + return ParsedStructure( + usernameId, + passwordId, + webDomain, + packageName, + ) + } +} + +private val autofillIdMapping = WeakHashMap() + +private fun getOrCreateAutofillIdMock(id: String): AutofillId { + val existingEntry = autofillIdMapping.entries.firstOrNull { entry -> entry.value == id } + if (existingEntry != null) { + return existingEntry.key + } + + val autofillId: AutofillId = mock() + autofillIdMapping[autofillId] = id + return autofillId +} + +internal val AutofillId.originalId: String + get() = autofillIdMapping[this] ?: throw AssertionError("Unknown AutofillId instance") diff --git a/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt new file mode 100644 index 0000000000..416a07ef67 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/java/mozilla/components/feature/autofill/test/MockStructure.kt @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.autofill.test + +import android.view.autofill.AutofillId +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.feature.autofill.handler.FillRequestHandlerTest +import mozilla.components.feature.autofill.structure.AutofillNodeNavigator +import mozilla.components.feature.autofill.structure.RawStructure +import java.io.File + +@ExperimentalCoroutinesApi +internal fun FillRequestHandlerTest.createMockStructure(filename: String, packageName: String): RawStructure { + val classLoader = javaClass.classLoader ?: throw RuntimeException("No class loader") + val resource = classLoader.getResource(filename) ?: throw RuntimeException("Resource not found") + val file = File(resource.path) + + return MockStructure(packageName, file) +} + +private class MockStructure( + private val packageName: String, + private val file: File, +) : RawStructure { + override val activityPackageName: String = packageName + + override fun createNavigator(): AutofillNodeNavigator<*, AutofillId> { + return DOMNavigator(file, packageName) + } +} diff --git a/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_expensify.xml b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_expensify.xml new file mode 100644 index 0000000000..0db81b3602 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_expensify.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook.xml b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook.xml new file mode 100644 index 0000000000..35cb055f52 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook_lite.xml b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook_lite.xml new file mode 100644 index 0000000000..ceae242125 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_facebook_lite.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_messenger_lite.xml b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_messenger_lite.xml new file mode 100644 index 0000000000..946a2bdc0f --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_messenger_lite.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_twitter.xml b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_twitter.xml new file mode 100644 index 0000000000..7d092fa1d1 --- /dev/null +++ b/mobile/android/android-components/components/feature/autofill/src/test/resources/fixtures/app_twitter.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + +