diff options
-rw-r--r-- | .idea/gradle.xml | 1 | ||||
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | app/build.gradle.kts | 1 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt | 105 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt | 132 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt | 8 | ||||
-rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt | 7 | ||||
-rw-r--r-- | app/src/main/res/layout/activity_oreo_autofill_filter.xml | 18 | ||||
-rw-r--r-- | app/src/main/res/layout/oreo_autofill_dataset.xml | 7 | ||||
-rw-r--r-- | app/src/main/res/values-de/strings.xml | 5 | ||||
-rw-r--r-- | app/src/main/res/values-es/strings.xml | 2 | ||||
-rw-r--r-- | app/src/main/res/values-fr/strings.xml | 6 | ||||
-rw-r--r-- | app/src/main/res/values-pt-rBR/strings.xml | 6 | ||||
-rw-r--r-- | app/src/main/res/values-ru/strings.xml | 6 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 9 | ||||
-rw-r--r-- | app/src/main/res/xml/oreo_autofill_service.xml | 1 |
16 files changed, 211 insertions, 107 deletions
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 @@ </set> </option> <option name="resolveModulePerSourceSet" value="false" /> + <option name="useQualifiedModuleNames" value="true" /> </GradleProjectSettings> </option> </component> 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<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) 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 @@ <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" diff --git a/app/src/main/res/layout/oreo_autofill_dataset.xml b/app/src/main/res/layout/oreo_autofill_dataset.xml index 2e9f48b9..e13d1688 100644 --- a/app/src/main/res/layout/oreo_autofill_dataset.xml +++ b/app/src/main/res/layout/oreo_autofill_dataset.xml @@ -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" diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index add23eda..82e75d68 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -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> 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 @@ <!-- 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> 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 @@ <string name="oreo_autofill_match_with">Apparier avec %1$s</string> <string name="oreo_autofill_matches_clear_existing">Effacer l’appairage 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> 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 @@ <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> 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 @@ <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> 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 @@ <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> 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 @@ <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" /> |