diff --git a/.idea/gradle.xml b/.idea/gradle.xml index be83284a..43336cba 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -17,6 +17,7 @@ diff --git a/CHANGELOG.md b/CHANGELOG.md index 312bfb02..d318df31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- On Android 11, Autofill will use the new [inline autofill](https://developer.android.com/guide/topics/text/ime-autofill#configure-provider) UI that integrates Autofill results into your keyboard app. + ### Fixed - Cancelling the Autofill "Generate password" action now correctly returns you to the original app. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e56ebd52..87e436a0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { compileOnly(Dependencies.AndroidX.annotation) implementation(Dependencies.AndroidX.activity_ktx) implementation(Dependencies.AndroidX.appcompat) + implementation(Dependencies.AndroidX.autofill) implementation(Dependencies.AndroidX.biometric) implementation(Dependencies.AndroidX.constraint_layout) implementation(Dependencies.AndroidX.core_ktx) diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt index ec3d2b77..2c62f935 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt @@ -12,7 +12,8 @@ import android.service.autofill.Dataset import android.service.autofill.FillCallback import android.service.autofill.FillResponse import android.service.autofill.SaveInfo -import android.widget.RemoteViews +import android.view.inputmethod.InlineSuggestionsRequest +import android.widget.inline.InlinePresentationSpec import androidx.annotation.RequiresApi import com.github.ajalt.timberkt.e import com.github.androidpasswordstore.autofillparser.AutofillAction @@ -41,70 +42,85 @@ class AutofillResponseBuilder(form: FillableForm) { scenario.fieldsToSave.minus(listOfNotNull(scenario.username)).isNotEmpty() private val canBeSaved = saveFlags != null && scenarioSupportsSave - private fun makePlaceholderDataset( - remoteView: RemoteViews, + private fun makeIntentDataset( + context: Context, + action: AutofillAction, intentSender: IntentSender, - action: AutofillAction + metadata: DatasetMetadata, + imeSpec: InlinePresentationSpec?, ): Dataset { - return Dataset.Builder(remoteView).run { + return Dataset.Builder(makeRemoteView(context, metadata)).run { fillWith(scenario, action, credentials = null) setAuthentication(intentSender) + if (imeSpec != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val inlinePresentation = makeInlinePresentation(context, imeSpec, metadata) + if (inlinePresentation != null) { + setInlinePresentation(inlinePresentation) + } + } build() } } - private fun makeMatchDataset(context: Context, file: File): Dataset? { + private fun makeMatchDataset(context: Context, file: File, imeSpec: InlinePresentationSpec?): Dataset? { if (scenario.fieldsToFillOn(AutofillAction.Match).isEmpty()) return null - val remoteView = makeFillMatchRemoteView(context, file, formOrigin) + val metadata = makeFillMatchMetadata(context, file) val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context) - return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Match) + return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata, imeSpec) } - private fun makeSearchDataset(context: Context): Dataset? { + private fun makeSearchDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? { if (scenario.fieldsToFillOn(AutofillAction.Search).isEmpty()) return null - val remoteView = makeSearchAndFillRemoteView(context, formOrigin) + val metadata = makeSearchAndFillMetadata(context) val intentSender = AutofillFilterView.makeMatchAndDecryptFileIntentSender(context, formOrigin) - return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Search) + return makeIntentDataset(context, AutofillAction.Search, intentSender, metadata, imeSpec) } - private fun makeGenerateDataset(context: Context): Dataset? { + private fun makeGenerateDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? { if (scenario.fieldsToFillOn(AutofillAction.Generate).isEmpty()) return null - val remoteView = makeGenerateAndFillRemoteView(context, formOrigin) + val metadata = makeGenerateAndFillMetadata(context) val intentSender = AutofillSaveActivity.makeSaveIntentSender(context, null, formOrigin) - return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Generate) + return makeIntentDataset(context, AutofillAction.Generate, intentSender, metadata, imeSpec) } - private fun makeFillOtpFromSmsDataset(context: Context): Dataset? { + private fun makeFillOtpFromSmsDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? { if (scenario.fieldsToFillOn(AutofillAction.FillOtpFromSms).isEmpty()) return null if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null - val remoteView = makeFillOtpFromSmsRemoteView(context, formOrigin) + val metadata = makeFillOtpFromSmsMetadata(context) val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context) - return makePlaceholderDataset(remoteView, intentSender, AutofillAction.FillOtpFromSms) + return makeIntentDataset(context, AutofillAction.FillOtpFromSms, intentSender, metadata, imeSpec) } private fun makePublisherChangedDataset( context: Context, - publisherChangedException: AutofillPublisherChangedException + publisherChangedException: AutofillPublisherChangedException, + imeSpec: InlinePresentationSpec? ): Dataset { - val remoteView = makeWarningRemoteView(context) + val metadata = makeWarningMetadata(context) // If the user decides to trust the new publisher, they can choose reset the list of // matches. In this case we need to immediately show a new `FillResponse` as if the app were // autofilled for the first time. This `FillResponse` needs to be returned as a result from // `AutofillPublisherChangedActivity`, which is why we create and pass it on here. - val fillResponseAfterReset = makeFillResponse(context, emptyList()) + val fillResponseAfterReset = makeFillResponse(context, null, emptyList()) val intentSender = AutofillPublisherChangedActivity.makePublisherChangedIntentSender( context, publisherChangedException, fillResponseAfterReset ) - return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Match) + return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata, imeSpec) } private fun makePublisherChangedResponse( context: Context, + inlineSuggestionsRequest: InlineSuggestionsRequest?, publisherChangedException: AutofillPublisherChangedException ): FillResponse { + val imeSpec = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + inlineSuggestionsRequest?.inlinePresentationSpecs?.firstOrNull() + } else { + null + } return FillResponse.Builder().run { - addDataset(makePublisherChangedDataset(context, publisherChangedException)) + addDataset(makePublisherChangedDataset(context, publisherChangedException, imeSpec)) setIgnoredIds(*ignoredIds.toTypedArray()) build() } @@ -127,28 +143,36 @@ class AutofillResponseBuilder(form: FillableForm) { } } - private fun makeFillResponse(context: Context, matchedFiles: List): FillResponse? { - var hasDataset = false + private fun makeFillResponse(context: Context, inlineSuggestionsRequest: InlineSuggestionsRequest?, matchedFiles: List): FillResponse? { + var datasetCount = 0 + val imeSpecs: List = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + inlineSuggestionsRequest?.inlinePresentationSpecs + } else { + null + } ?: emptyList() return FillResponse.Builder().run { for (file in matchedFiles) { - makeMatchDataset(context, file)?.let { - hasDataset = true + makeMatchDataset(context, file, imeSpecs.getOrNull(datasetCount))?.let { + datasetCount++ addDataset(it) } } - makeSearchDataset(context)?.let { - hasDataset = true + makeSearchDataset(context, imeSpecs.getOrNull(datasetCount))?.let { + datasetCount++ addDataset(it) } - makeGenerateDataset(context)?.let { - hasDataset = true + makeGenerateDataset(context, imeSpecs.getOrNull(datasetCount))?.let { + datasetCount++ addDataset(it) } - makeFillOtpFromSmsDataset(context)?.let { - hasDataset = true + makeFillOtpFromSmsDataset(context, imeSpecs.getOrNull(datasetCount))?.let { + datasetCount++ addDataset(it) } - if (!hasDataset) return null + if (datasetCount == 0) return null + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + setHeader(makeRemoteView(context, makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true)))) + } makeSaveInfo()?.let { setSaveInfo(it) } setClientState(clientState) setIgnoredIds(*ignoredIds.toTypedArray()) @@ -159,14 +183,14 @@ class AutofillResponseBuilder(form: FillableForm) { /** * Creates and returns a suitable [FillResponse] to the Autofill framework. */ - fun fillCredentials(context: Context, callback: FillCallback) { + fun fillCredentials(context: Context, inlineSuggestionsRequest: InlineSuggestionsRequest?, callback: FillCallback) { AutofillMatcher.getMatchesFor(context, formOrigin).fold( success = { matchedFiles -> - callback.onSuccess(makeFillResponse(context, matchedFiles)) + callback.onSuccess(makeFillResponse(context, inlineSuggestionsRequest, matchedFiles)) }, failure = { e -> e(e) - callback.onSuccess(makePublisherChangedResponse(context, e)) + callback.onSuccess(makePublisherChangedResponse(context, inlineSuggestionsRequest, e)) } ) } @@ -178,14 +202,17 @@ class AutofillResponseBuilder(form: FillableForm) { clientState: Bundle, action: AutofillAction ): Dataset { - val remoteView = makePlaceholderRemoteView(context) val scenario = AutofillScenario.fromBundle(clientState) // Before Android P, Datasets used for fill-in had to come with a RemoteViews, even - // though they are never shown. + // though they are rarely shown. + // FIXME: We should clone the original dataset here and add the credentials to be filled + // in. Otherwise, the entry in the cached list of datasets will be overwritten by the + // fill-in dataset without any visual representation. This causes it to be missing from + // the Autofill suggestions shown after the user clears the filled out form fields. val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { Dataset.Builder() } else { - Dataset.Builder(remoteView) + Dataset.Builder(makeRemoteView(context, makeEmptyMetadata())) } return builder.run { if (scenario != null) fillWith(scenario, action, credentials) diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt index 5e8061a2..49e0d3e3 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt @@ -4,64 +4,110 @@ */ package com.zeapo.pwdstore.autofill.oreo +import android.annotation.SuppressLint +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.graphics.drawable.Icon +import android.os.Build +import android.service.autofill.InlinePresentation +import android.view.View import android.widget.RemoteViews -import com.github.androidpasswordstore.autofillparser.FormOrigin +import android.widget.inline.InlinePresentationSpec +import androidx.annotation.DrawableRes +import androidx.autofill.inline.UiVersions +import androidx.autofill.inline.v1.InlineSuggestionUi +import com.zeapo.pwdstore.PasswordStore import com.zeapo.pwdstore.R import com.zeapo.pwdstore.utils.PasswordRepository import java.io.File -private fun makeRemoteView( - context: Context, - title: String, - summary: String, - iconRes: Int -): RemoteViews { +data class DatasetMetadata(val title: String, val subtitle: String?, @DrawableRes val iconRes: Int) + +fun makeRemoteView(context: Context, metadata: DatasetMetadata): RemoteViews { return RemoteViews(context.packageName, R.layout.oreo_autofill_dataset).apply { - setTextViewText(R.id.title, title) - setTextViewText(R.id.summary, summary) - setImageViewResource(R.id.icon, iconRes) + setTextViewText(R.id.title, metadata.title) + if (metadata.subtitle != null) { + setTextViewText(R.id.summary, metadata.subtitle) + } else { + setViewVisibility(R.id.summary, View.GONE) + } + if (metadata.iconRes != Resources.ID_NULL) { + setImageViewResource(R.id.icon, metadata.iconRes) + } else { + setViewVisibility(R.id.icon, View.GONE) + } } } -fun makeFillMatchRemoteView(context: Context, file: File, formOrigin: FormOrigin): RemoteViews { - val title = formOrigin.getPrettyIdentifier(context, untrusted = false) +@SuppressLint("RestrictedApi") +fun makeInlinePresentation(context: Context, imeSpec: InlinePresentationSpec, metadata: DatasetMetadata): InlinePresentation? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) + return null + + if (UiVersions.INLINE_UI_VERSION_1 !in UiVersions.getVersions(imeSpec.style)) + return null + + val launchIntent = PendingIntent.getActivity(context, 0, Intent(context, PasswordStore::class.java), 0) + val slice = InlineSuggestionUi.newContentBuilder(launchIntent).run { + setTitle(metadata.title) + if (metadata.subtitle != null) + setSubtitle(metadata.subtitle) + setContentDescription(if (metadata.subtitle != null) "${metadata.title} - ${metadata.subtitle}" else metadata.title) + setStartIcon(Icon.createWithResource(context, metadata.iconRes)) + build().slice + } + + return InlinePresentation(slice, imeSpec, false) +} + + +fun makeFillMatchMetadata(context: Context, file: File): DatasetMetadata { val directoryStructure = AutofillPreferences.directoryStructure(context) val relativeFile = file.relativeTo(PasswordRepository.getRepositoryDirectory()) - val summary = directoryStructure.getUsernameFor(relativeFile) - ?: directoryStructure.getPathToIdentifierFor(relativeFile) ?: "" - val iconRes = R.drawable.ic_person_black_24dp - return makeRemoteView(context, title, summary, iconRes) + val title = directoryStructure.getIdentifierFor(relativeFile) + ?: directoryStructure.getAccountPartFor(relativeFile)!! + val subtitle = directoryStructure.getAccountPartFor(relativeFile) + return DatasetMetadata( + title, + subtitle, + R.drawable.ic_person_black_24dp + ) } -fun makeSearchAndFillRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews { - val title = formOrigin.getPrettyIdentifier(context, untrusted = true) - val summary = context.getString(R.string.oreo_autofill_search_in_store) - val iconRes = R.drawable.ic_search_black_24dp - return makeRemoteView(context, title, summary, iconRes) -} +fun makeSearchAndFillMetadata(context: Context) = DatasetMetadata( + context.getString(R.string.oreo_autofill_search_in_store), + null, + R.drawable.ic_search_black_24dp +) -fun makeGenerateAndFillRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews { - val title = formOrigin.getPrettyIdentifier(context, untrusted = true) - val summary = context.getString(R.string.oreo_autofill_generate_password) - val iconRes = R.drawable.ic_autofill_new_password - return makeRemoteView(context, title, summary, iconRes) -} +fun makeGenerateAndFillMetadata(context: Context) = DatasetMetadata( + context.getString(R.string.oreo_autofill_generate_password), + null, + R.drawable.ic_autofill_new_password +) -fun makeFillOtpFromSmsRemoteView(context: Context, formOrigin: FormOrigin): RemoteViews { - val title = formOrigin.getPrettyIdentifier(context, untrusted = true) - val summary = context.getString(R.string.oreo_autofill_fill_otp_from_sms) - val iconRes = R.drawable.ic_autofill_sms - return makeRemoteView(context, title, summary, iconRes) -} +fun makeFillOtpFromSmsMetadata(context: Context) = DatasetMetadata( + context.getString(R.string.oreo_autofill_fill_otp_from_sms), + null, + R.drawable.ic_autofill_sms +) -fun makePlaceholderRemoteView(context: Context): RemoteViews { - return makeRemoteView(context, "PLACEHOLDER", "PLACEHOLDER", R.mipmap.ic_launcher) -} +fun makeEmptyMetadata() = DatasetMetadata( + "PLACEHOLDER", + "PLACEHOLDER", + R.mipmap.ic_launcher +) -fun makeWarningRemoteView(context: Context): RemoteViews { - val title = context.getString(R.string.oreo_autofill_warning_publisher_dataset_title) - val summary = context.getString(R.string.oreo_autofill_warning_publisher_dataset_summary) - val iconRes = R.drawable.ic_warning_red_24dp - return makeRemoteView(context, title, summary, iconRes) -} +fun makeWarningMetadata(context: Context) = DatasetMetadata( + context.getString(R.string.oreo_autofill_warning_publisher_dataset_title), + context.getString(R.string.oreo_autofill_warning_publisher_dataset_summary), + R.drawable.ic_warning_red_24dp +) + +fun makeHeaderMetadata(title: String) = DatasetMetadata( + title, + null, + 0 +) diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt index ecec6747..10831cc5 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt @@ -86,7 +86,13 @@ class OreoAutofillService : AutofillService() { callback.onSuccess(null) return } - AutofillResponseBuilder(formToFill).fillCredentials(this, callback) + val inlineSuggestionsRequest = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + request.inlineSuggestionsRequest + } else { + null + } + AutofillResponseBuilder(formToFill).fillCredentials(this, inlineSuggestionsRequest, callback) } override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt index f22c6596..25919ad5 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt @@ -162,6 +162,13 @@ class AutofillFilterView : AppCompatActivity() { setText(initialSearch, TextView.BufferType.EDITABLE) addTextChangedListener { updateSearch() } } + origin.text = buildSpannedString { + append(getString(R.string.oreo_autofill_select_and_fill_into)) + append("\n") + bold { + append(formOrigin.getPrettyIdentifier(applicationContext, untrusted = true)) + } + } strictDomainSearch.apply { visibility = if (formOrigin is FormOrigin.Web) View.VISIBLE else View.GONE isChecked = formOrigin is FormOrigin.Web diff --git a/app/src/main/res/layout/activity_oreo_autofill_filter.xml b/app/src/main/res/layout/activity_oreo_autofill_filter.xml index a3718b43..8e671e30 100644 --- a/app/src/main/res/layout/activity_oreo_autofill_filter.xml +++ b/app/src/main/res/layout/activity_oreo_autofill_filter.xml @@ -14,16 +14,28 @@ + + + app:layout_constraintTop_toBottomOf="@id/origin"> Passwort unverschlüsselt senden an… App Icon + Eintrag auswählen für Autofill in Phishing-resistente Suche Keine Ergebnisse. Speichern aufgrund eines internen Fehlers fehlgeschlagen Diese App wird derzeit nicht unterstützt Die Passwörter stimmen nicht überein - Passwort generieren… + Eintrag erstellen + Eintrag suchen + Code aus SMS einfügen Die derzeit installierte App versucht, Ihre Anmeldeinformationen zu stehlen, indem sie vorgibt, eine vertrauenswürdige App zu sein.\n\nVersuchen Sie die App zu deinstallieren und installieren Sie sie erneut aus einer vertrauenswürdigen Quelle wie dem Play Store, Amazon Appstore, F-Droid oder dem Shop Ihres Telefonherstellers. Installiert: %1$s Warnung diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c64287b5..f19b6817 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -130,11 +130,9 @@ Coincide con %1$s Sin resultados. - Buscar en la tienda… Error al guardar debido a un error interno Esta aplicación no es compatible actualmente Las contraseñas no coinciden - Generar contraseña… Instalada: %1$s Información avanzada Mantener el Autocompletado desactivado diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 557591e8..10fa1130 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -185,17 +185,15 @@ Apparier avec %1$s Effacer l’appairage actuel Aucun résultat. - Rechercher dans le dépôt… Échec de la sauvegarde : erreur interne Cette application n\'est actuellement pas prise en charge Les mots de passe ne coïncident pas - Générer un mot de passe… - Extraire le code depuis un SMS… + Extraire le code depuis un SMS L\'éditeur de cette application a changé depuis que vous avez appairé un mot de passe avec celle-ci: L\'application actuellement installée peut essayer de voler vos identifiants en faisant semblant d\'être une application de confiance.\n\nEssayez de désinstaller et de réinstaller l\'application à partir d\'une source fiable, comme le Play Store, l\'AppStore d\'Amazon, le F-Droid ou la boutique du fabricant de votre téléphone. Installé : %1$s Avertissement - Appuyez pour en savoir plus… + Appuyez pour en savoir plus Tentative possible d\'hameçonnage Remplir et enregistrer les identifiants Remplir les identifiants diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index cef14227..c45b2705 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -187,12 +187,10 @@ Combinar com %1$s Limpar correspondências existentes Sem resultados. - Pesquisar no armazenamento… Falha ao salvar devido a um erro interno Este app não é suportado no momento As senhas não coincidem - Gerar senha… - Extrair código do SMS… + Extrair código do SMS Número máximo de correspondências (%1$d) atingidas; limpar correspondências antes de adicionar novas. O editor deste aplicativo mudou desde a primeira vez que você associou uma entrada no Password Store: O aplicativo atualmente instalado pode estar tentando roubar suas credenciais fingindo ser um aplicativo confiável.\n\nTente desinstalar e reinstalar o aplicativo de uma fonte confiável, como a Play Store, Amazon Appstore, F-Droid ou a loja do fabricante do seu telefone. @@ -201,7 +199,7 @@ Manter o preenchimento automático desativado Reativar preenchimento automático Alerta - Toque para detalhes… + Toque para detalhes Possível tentativa de phishing Preencher e salvar credenciais Preencher as credenciais diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f9340b41..baa49c74 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -191,12 +191,10 @@ Совпадает с %1$s Очистить существующие совпадения Не найдено. - Искать в хранилище... Сохранение не удалось из-за внутренней ошибки Это приложение в настоящее время не поддерживается Пароли не совпадают - Сгенерировать пароль... - Извлечение кодов из SMS… + Извлечение кодов из SMS Достигнуто максимальное количество совпадений (%1$d); очистите совпадения перед тем как добавите новые. Издатель приложения изменился с тех пор как вы первый раз связали с ним запись хранилища паролей: Установленное приложение может попытаться украсть ваши учетные данные, выдавая себя за доверенное приложение\n\nПопробуйте удалить или переустановить  приложение из доверенного источника, такого как Play Store, Amazon Appstore, F-Droid или магазин приложений производителя вашего смартфона. @@ -205,7 +203,7 @@ Оставить автозаполнение отключенным Включить автозаполнение снова Предупреждение - Нажмите для получения подробностей... + Нажмите для получения подробностей Возможная попытка фишинга Заполнить и сохранить учетные данные Заполнить учетные данные diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea222409..9c87c1da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -231,16 +231,17 @@ App icon + Select entry to fill into Phishing-resistant search Match with %1$s Clear existing matches No results. - Search in store… + Search entry Save failed due to an internal error This app is currently not supported Passwords don\'t match - Generate password… - Extract code from SMS… + Create entry + Extract code from SMS Maximum number of matches (%1$d) reached; clear matches before adding new ones. This app\'s publisher has changed since you first associated a Password Store entry with it: The currently installed app may be trying to steal your credentials by pretending to be a trusted app.\n\nTry to uninstall and reinstall the app from a trusted source, such as the Play Store, Amazon Appstore, F-Droid, or your phone manufacturer\'s store. @@ -250,7 +251,7 @@ Keep Autofill disabled Re-enable Autofill Warning - Tap for details… + Tap for details Possible phishing attempt Fill and save credentials Fill credentials diff --git a/app/src/main/res/xml/oreo_autofill_service.xml b/app/src/main/res/xml/oreo_autofill_service.xml index 00736cd5..b8a7510b 100644 --- a/app/src/main/res/xml/oreo_autofill_service.xml +++ b/app/src/main/res/xml/oreo_autofill_service.xml @@ -5,6 +5,7 @@