Improve Autofill UI and enable inline presentations (#1181)

* Improve Autofill UI and enable inline presentations

Improves the Autofill UI in the following ways:

* Add support for Android 11 inline presentations of Autofill datasets.
* Instead of showing the identifier (app name or web origin) of the
current app on top of every Autofill dataset, it is now shown 1) as a
header dataset on Android 9 and 10 as well as 2) at the top of the
search activity on all supported versions of Android. Rationale: The
identifier is only used in trust decisions when choosing an existing
entry to fill and should feature prominently in that view, not
elsewhere.
* Show the actual identifier part of a matched entry's path, which may
differ from the identifier of the matched app/website.
* Slightly tweak the labels of Search/Generate Autofill actions to
indicate that a) this is about entries and b) the user may skip the
generation of a password and supply a custom one as well.

* Suppress lint error

* Address review comments

* Add a fixme about properly handling fill-in datasets

* CHANGELOG: add entry for inline presentation

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Remove unused parameter

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Fabian Henneke 2020-11-02 20:25:37 +01:00 committed by GitHub
parent cff8d41c91
commit 1d13a1fbd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 211 additions and 107 deletions

View file

@ -17,6 +17,7 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

View file

@ -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.

View file

@ -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)

View file

@ -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<File>): FillResponse? {
var hasDataset = false
private fun makeFillResponse(context: Context, inlineSuggestionsRequest: InlineSuggestionsRequest?, matchedFiles: List<File>): FillResponse? {
var datasetCount = 0
val imeSpecs: List<InlinePresentationSpec> = 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)

View file

@ -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
)

View file

@ -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) {

View file

@ -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

View file

@ -14,16 +14,28 @@
<ImageView
android:id="@+id/cover"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_height="60dp"
android:background="@color/primary_color"
android:contentDescription="@string/app_name"
android:src="@mipmap/ic_launcher_foreground"
app:layout_constraintBottom_toTopOf="@id/searchLayout"
app:layout_constraintBottom_toTopOf="@id/origin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/origin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@id/searchLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cover"
app:layout_constraintVertical_bias="0.0" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/searchLayout"
android:layout_width="match_parent"
@ -35,7 +47,7 @@
app:layout_constraintBottom_toTopOf="@id/rvPasswordSwitcher"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cover">
app:layout_constraintTop_toBottomOf="@id/origin">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/search"

View file

@ -6,9 +6,10 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
@ -20,6 +21,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="10dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:adjustViewBounds="true"
android:maxWidth="20dp"
android:maxHeight="20dp"

View file

@ -157,12 +157,15 @@
<string name="send_plaintext_password_to">Passwort unverschlüsselt senden an…</string>
<string name="app_icon_hint">App Icon</string>
<!-- Oreo Autofill -->
<string name="oreo_autofill_select_and_fill_into">Eintrag auswählen für Autofill in</string>
<string name="oreo_autofill_strict_domain_search">Phishing-resistente Suche</string>
<string name="oreo_autofill_filter_no_results">Keine Ergebnisse.</string>
<string name="oreo_autofill_save_internal_error">Speichern aufgrund eines internen Fehlers fehlgeschlagen</string>
<string name="oreo_autofill_save_app_not_supported">Diese App wird derzeit nicht unterstützt</string>
<string name="oreo_autofill_save_passwords_dont_match">Die Passwörter stimmen nicht überein</string>
<string name="oreo_autofill_generate_password">Passwort generieren…</string>
<string name="oreo_autofill_generate_password">Eintrag erstellen</string>
<string name="oreo_autofill_search_in_store">Eintrag suchen</string>
<string name="oreo_autofill_fill_otp_from_sms">Code aus SMS einfügen</string>
<string name="oreo_autofill_warning_publisher_footer"><b>Die derzeit installierte App versucht, Ihre Anmeldeinformationen zu stehlen, indem sie vorgibt, eine vertrauenswürdige App zu sein.</b>\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.</string>
<string name="oreo_autofill_warning_publisher_install_time">Installiert: %1$s</string>
<string name="oreo_autofill_warning_publisher_warning_sign_description">Warnung</string>

View file

@ -130,11 +130,9 @@
<!-- Oreo Autofill -->
<string name="oreo_autofill_match_with">Coincide con %1$s</string>
<string name="oreo_autofill_filter_no_results">Sin resultados.</string>
<string name="oreo_autofill_search_in_store">Buscar en la tienda…</string>
<string name="oreo_autofill_save_internal_error">Error al guardar debido a un error interno</string>
<string name="oreo_autofill_save_app_not_supported">Esta aplicación no es compatible actualmente</string>
<string name="oreo_autofill_save_passwords_dont_match">Las contraseñas no coinciden</string>
<string name="oreo_autofill_generate_password">Generar contraseña…</string>
<string name="oreo_autofill_warning_publisher_install_time">Instalada: %1$s</string>
<string name="oreo_autofill_warning_publisher_advanced_info_button">Información avanzada</string>
<string name="oreo_autofill_warning_publisher_changed_disable_autofill_button">Mantener el Autocompletado desactivado</string>

View file

@ -185,17 +185,15 @@
<string name="oreo_autofill_match_with">Apparier avec %1$s</string>
<string name="oreo_autofill_matches_clear_existing">Effacer lappairage actuel</string>
<string name="oreo_autofill_filter_no_results">Aucun résultat.</string>
<string name="oreo_autofill_search_in_store">Rechercher dans le dépôt…</string>
<string name="oreo_autofill_save_internal_error">Échec de la sauvegarde : erreur interne</string>
<string name="oreo_autofill_save_app_not_supported">Cette application n\'est actuellement pas prise en charge</string>
<string name="oreo_autofill_save_passwords_dont_match">Les mots de passe ne coïncident pas</string>
<string name="oreo_autofill_generate_password">Générer un mot de passe…</string>
<string name="oreo_autofill_fill_otp_from_sms">Extraire le code depuis un SMS…</string>
<string name="oreo_autofill_fill_otp_from_sms">Extraire le code depuis un SMS</string>
<string name="oreo_autofill_warning_publisher_header">L\'éditeur de cette application a changé depuis que vous avez appairé un mot de passe avec celle-ci:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>L\'application actuellement installée peut essayer de voler vos identifiants en faisant semblant d\'être une application de confiance.</b>\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.</string>
<string name="oreo_autofill_warning_publisher_install_time">Installé : %1$s</string>
<string name="oreo_autofill_warning_publisher_warning_sign_description">Avertissement</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Appuyez pour en savoir plus</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Appuyez pour en savoir plus</string>
<string name="oreo_autofill_warning_publisher_dataset_title">Tentative possible d\'hameçonnage</string>
<string name="oreo_autofill_general_fill_and_save_support">Remplir et enregistrer les identifiants</string>
<string name="oreo_autofill_general_fill_support">Remplir les identifiants</string>

View file

@ -187,12 +187,10 @@
<string name="oreo_autofill_match_with">Combinar com %1$s</string>
<string name="oreo_autofill_matches_clear_existing">Limpar correspondências existentes</string>
<string name="oreo_autofill_filter_no_results">Sem resultados.</string>
<string name="oreo_autofill_search_in_store">Pesquisar no armazenamento…</string>
<string name="oreo_autofill_save_internal_error">Falha ao salvar devido a um erro interno</string>
<string name="oreo_autofill_save_app_not_supported">Este app não é suportado no momento</string>
<string name="oreo_autofill_save_passwords_dont_match">As senhas não coincidem</string>
<string name="oreo_autofill_generate_password">Gerar senha…</string>
<string name="oreo_autofill_fill_otp_from_sms">Extrair código do SMS…</string>
<string name="oreo_autofill_fill_otp_from_sms">Extrair código do SMS</string>
<string name="oreo_autofill_max_matches_reached">Número máximo de correspondências (%1$d) atingidas; limpar correspondências antes de adicionar novas.</string>
<string name="oreo_autofill_warning_publisher_header">O editor deste aplicativo mudou desde a primeira vez que você associou uma entrada no Password Store:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>O aplicativo atualmente instalado pode estar tentando roubar suas credenciais fingindo ser um aplicativo confiável.</b>\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.</string>
@ -201,7 +199,7 @@
<string name="oreo_autofill_warning_publisher_changed_disable_autofill_button">Manter o preenchimento automático desativado</string>
<string name="oreo_autofill_warning_publisher_reenable_button">Reativar preenchimento automático</string>
<string name="oreo_autofill_warning_publisher_warning_sign_description">Alerta</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Toque para detalhes</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Toque para detalhes</string>
<string name="oreo_autofill_warning_publisher_dataset_title">Possível tentativa de phishing</string>
<string name="oreo_autofill_general_fill_and_save_support">Preencher e salvar credenciais</string>
<string name="oreo_autofill_general_fill_support">Preencher as credenciais</string>

View file

@ -191,12 +191,10 @@
<string name="oreo_autofill_match_with">Совпадает с %1$s</string>
<string name="oreo_autofill_matches_clear_existing">Очистить существующие совпадения</string>
<string name="oreo_autofill_filter_no_results">Не найдено.</string>
<string name="oreo_autofill_search_in_store">Искать в хранилище...</string>
<string name="oreo_autofill_save_internal_error">Сохранение не удалось из-за внутренней ошибки</string>
<string name="oreo_autofill_save_app_not_supported">Это приложение в настоящее время не поддерживается</string>
<string name="oreo_autofill_save_passwords_dont_match">Пароли не совпадают</string>
<string name="oreo_autofill_generate_password">Сгенерировать пароль...</string>
<string name="oreo_autofill_fill_otp_from_sms">Извлечение кодов из SMS…</string>
<string name="oreo_autofill_fill_otp_from_sms">Извлечение кодов из SMS</string>
<string name="oreo_autofill_max_matches_reached">Достигнуто максимальное количество совпадений (%1$d); очистите совпадения перед тем как добавите новые.</string>
<string name="oreo_autofill_warning_publisher_header">Издатель приложения изменился с тех пор как вы первый раз связали с ним запись хранилища паролей:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>Установленное приложение может попытаться украсть ваши учетные данные, выдавая себя за доверенное приложение</b>\n\nПопробуйте удалить или переустановить  приложение из доверенного источника, такого как Play Store, Amazon Appstore, F-Droid или магазин приложений производителя вашего смартфона.</string>
@ -205,7 +203,7 @@
<string name="oreo_autofill_warning_publisher_changed_disable_autofill_button">Оставить автозаполнение отключенным</string>
<string name="oreo_autofill_warning_publisher_reenable_button">Включить автозаполнение снова</string>
<string name="oreo_autofill_warning_publisher_warning_sign_description">Предупреждение</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Нажмите для получения подробностей...</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Нажмите для получения подробностей</string>
<string name="oreo_autofill_warning_publisher_dataset_title">Возможная попытка фишинга</string>
<string name="oreo_autofill_general_fill_and_save_support">Заполнить и сохранить учетные данные</string>
<string name="oreo_autofill_general_fill_support">Заполнить учетные данные</string>

View file

@ -231,16 +231,17 @@
<string name="app_icon_hint">App icon</string>
<!-- Oreo Autofill -->
<string name="oreo_autofill_select_and_fill_into">Select entry to fill into</string>
<string name="oreo_autofill_strict_domain_search">Phishing-resistant search</string>
<string name="oreo_autofill_match_with">Match with %1$s</string>
<string name="oreo_autofill_matches_clear_existing">Clear existing matches</string>
<string name="oreo_autofill_filter_no_results">No results.</string>
<string name="oreo_autofill_search_in_store">Search in store…</string>
<string name="oreo_autofill_search_in_store">Search entry</string>
<string name="oreo_autofill_save_internal_error">Save failed due to an internal error</string>
<string name="oreo_autofill_save_app_not_supported">This app is currently not supported</string>
<string name="oreo_autofill_save_passwords_dont_match">Passwords don\'t match</string>
<string name="oreo_autofill_generate_password">Generate password…</string>
<string name="oreo_autofill_fill_otp_from_sms">Extract code from SMS</string>
<string name="oreo_autofill_generate_password">Create entry</string>
<string name="oreo_autofill_fill_otp_from_sms">Extract code from SMS</string>
<string name="oreo_autofill_max_matches_reached">Maximum number of matches (%1$d) reached; clear matches before adding new ones.</string>
<string name="oreo_autofill_warning_publisher_header">This app\'s publisher has changed since you first associated a Password Store entry with it:</string>
<string name="oreo_autofill_warning_publisher_footer"><b>The currently installed app may be trying to steal your credentials by pretending to be a trusted app.</b>\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.</string>
@ -250,7 +251,7 @@
<string name="oreo_autofill_warning_publisher_changed_disable_autofill_button">Keep Autofill disabled</string>
<string name="oreo_autofill_warning_publisher_reenable_button">Re-enable Autofill</string>
<string name="oreo_autofill_warning_publisher_warning_sign_description">Warning</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Tap for details</string>
<string name="oreo_autofill_warning_publisher_dataset_summary">Tap for details</string>
<string name="oreo_autofill_warning_publisher_dataset_title">Possible phishing attempt</string>
<string name="oreo_autofill_general_fill_and_save_support">Fill and save credentials</string>
<string name="oreo_autofill_general_fill_support">Fill credentials</string>

View file

@ -5,6 +5,7 @@
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:supportsInlineSuggestions="true"
tools:ignore="UnusedAttribute">
<compatibility-package android:name="com.android.chrome" />
<compatibility-package android:name="com.brave.browser" />