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:
parent
cff8d41c91
commit
1d13a1fbd6
16 changed files with 211 additions and 107 deletions
|
@ -17,6 +17,7 @@
|
|||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
<option name="useQualifiedModuleNames" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in a new issue