fix: Autofill implementation

This commit is contained in:
J-Jamet
2023-09-04 19:16:16 +02:00
parent 1e77a42c93
commit 6d633c9986
4 changed files with 122 additions and 64 deletions

View File

@@ -1,7 +1,6 @@
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.view.inputmethod.InlineSuggestionsRequest
data class AutofillComponent(val assistStructure: AssistStructure, data class AutofillComponent(val assistStructure: AssistStructure,
val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?) val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?)

View File

@@ -119,7 +119,7 @@ object AutofillHelper {
setField( setField(
id, autofillValue?.let { id, autofillValue?.let {
Field.Builder() Field.Builder()
.setValue(autofillValue) .setValue(it)
.build() .build()
} }
) )
@@ -127,6 +127,7 @@ object AutofillHelper {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
setValue(id, autofillValue) setValue(id, autofillValue)
} }
Log.d(TAG, "Set Autofill value $autofillValue for id $id")
return this return this
} }
@@ -134,8 +135,8 @@ object AutofillHelper {
database: ContextualDatabase, database: ContextualDatabase,
entryInfo: EntryInfo, entryInfo: EntryInfo,
struct: StructureParser.Result, struct: StructureParser.Result,
inlinePresentation: InlinePresentation?): Dataset.Builder { inlinePresentation: InlinePresentation?): Dataset {
val remoteViews = newRemoteViews(context, database, makeEntryTitle(entryInfo), entryInfo.icon) val remoteViews: RemoteViews = newRemoteViews(context, database, makeEntryTitle(entryInfo), entryInfo.icon)
val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Dataset.Builder(Presentations.Builder() Dataset.Builder(Presentations.Builder()
@@ -145,6 +146,7 @@ object AutofillHelper {
} }
} }
.setDialogPresentation(remoteViews) .setDialogPresentation(remoteViews)
.setMenuPresentation(remoteViews)
.build()) .build())
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -160,10 +162,16 @@ object AutofillHelper {
datasetBuilder.setId(entryInfo.id.toString()) datasetBuilder.setId(entryInfo.id.toString())
struct.usernameId?.let { usernameId -> struct.usernameId?.let { usernameId ->
datasetBuilder.addValueToDatasetBuilder(usernameId, AutofillValue.forText(entryInfo.username)) datasetBuilder.addValueToDatasetBuilder(
usernameId,
AutofillValue.forText(entryInfo.username)
)
} }
struct.passwordId?.let { passwordId -> struct.passwordId?.let { passwordId ->
datasetBuilder.addValueToDatasetBuilder(passwordId, AutofillValue.forText(entryInfo.password)) datasetBuilder.addValueToDatasetBuilder(
passwordId,
AutofillValue.forText(entryInfo.password)
)
} }
if (entryInfo.expires) { if (entryInfo.expires) {
@@ -176,9 +184,15 @@ object AutofillHelper {
struct.creditCardExpirationDateId?.let { struct.creditCardExpirationDateId?.let {
if (struct.isWebView) { if (struct.isWebView) {
// set date string as defined in https://html.spec.whatwg.org // set date string as defined in https://html.spec.whatwg.org
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forText("$year\u002D$monthString")) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText("$year\u002D$monthString")
)
} else { } else {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forDate(entryInfo.expiryTime.date.time)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forDate(entryInfo.expiryTime.date.time)
)
} }
} }
struct.creditCardExpirationYearId?.let { struct.creditCardExpirationYearId?.let {
@@ -192,34 +206,58 @@ object AutofillHelper {
} }
if (yearIndex != -1) { if (yearIndex != -1) {
autofillValue = AutofillValue.forList(yearIndex) autofillValue = AutofillValue.forList(yearIndex)
datasetBuilder.addValueToDatasetBuilder(it, autofillValue) datasetBuilder.addValueToDatasetBuilder(
it,
autofillValue
)
} }
} }
if (autofillValue == null) { if (autofillValue == null) {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forText(year.toString())) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(year.toString())
)
} }
} }
struct.creditCardExpirationMonthId?.let { struct.creditCardExpirationMonthId?.let {
if (struct.isWebView) { if (struct.isWebView) {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forText(monthString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(monthString)
)
} else { } else {
if (struct.creditCardExpirationMonthOptions != null) { if (struct.creditCardExpirationMonthOptions != null) {
// index starts at 0 // index starts at 0
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forList(month - 1)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forList(month - 1)
)
} else { } else {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forText(monthString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(monthString)
)
} }
} }
} }
struct.creditCardExpirationDayId?.let { struct.creditCardExpirationDayId?.let {
if (struct.isWebView) { if (struct.isWebView) {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forText(dayString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(dayString)
)
} else { } else {
if (struct.creditCardExpirationDayOptions != null) { if (struct.creditCardExpirationDayOptions != null) {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forList(day - 1)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forList(day - 1)
)
} else { } else {
datasetBuilder.addValueToDatasetBuilder(it, AutofillValue.forText(dayString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(dayString)
)
} }
} }
} }
@@ -227,22 +265,32 @@ object AutofillHelper {
for (field in entryInfo.customFields) { for (field in entryInfo.customFields) {
if (field.name == TemplateField.LABEL_HOLDER) { if (field.name == TemplateField.LABEL_HOLDER) {
struct.creditCardHolderId?.let { ccNameId -> struct.creditCardHolderId?.let { ccNameId ->
datasetBuilder.addValueToDatasetBuilder(ccNameId, AutofillValue.forText(field.protectedValue.stringValue)) datasetBuilder.addValueToDatasetBuilder(
ccNameId,
AutofillValue.forText(field.protectedValue.stringValue)
)
} }
} }
if (field.name == TemplateField.LABEL_NUMBER) { if (field.name == TemplateField.LABEL_NUMBER) {
struct.creditCardNumberId?.let { ccnId -> struct.creditCardNumberId?.let { ccnId ->
datasetBuilder.addValueToDatasetBuilder(ccnId, AutofillValue.forText(field.protectedValue.stringValue)) datasetBuilder.addValueToDatasetBuilder(
ccnId,
AutofillValue.forText(field.protectedValue.stringValue)
)
} }
} }
if (field.name == TemplateField.LABEL_CVV) { if (field.name == TemplateField.LABEL_CVV) {
struct.cardVerificationValueId?.let { cvvId -> struct.cardVerificationValueId?.let { cvvId ->
datasetBuilder.addValueToDatasetBuilder(cvvId, AutofillValue.forText(field.protectedValue.stringValue)) datasetBuilder.addValueToDatasetBuilder(
cvvId,
AutofillValue.forText(field.protectedValue.stringValue)
)
} }
} }
} }
val dataset = datasetBuilder.build()
return datasetBuilder Log.d(TAG, "Autofill Dataset $dataset created")
return dataset
} }
/** /**
@@ -388,7 +436,7 @@ object AutofillHelper {
} }
// Create dataset for each entry // Create dataset for each entry
responseBuilder.addDataset( responseBuilder.addDataset(
buildDatasetForEntry(context, database, entry, parseResult, inlinePresentation).build() buildDatasetForEntry(context, database, entry, parseResult, inlinePresentation)
) )
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to add dataset") Log.e(TAG, "Unable to add dataset")
@@ -407,52 +455,50 @@ object AutofillHelper {
val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context, val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context,
searchInfo, compatInlineSuggestionsRequest) searchInfo, compatInlineSuggestionsRequest)
var inlinePresentation: InlinePresentation? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0]
inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent)
}
}
val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Dataset.Builder(Presentations.Builder()
.apply {
inlinePresentation?.let {
setInlinePresentation(it)
}
}
.setDialogPresentation(manualSelectionView)
.setMenuPresentation(manualSelectionView)
.build())
} else {
@Suppress("DEPRECATION")
Dataset.Builder(manualSelectionView).apply {
inlinePresentation?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setInlinePresentation(it)
}
}
}
}
parseResult.allAutofillIds().let { autofillIds -> parseResult.allAutofillIds().let { autofillIds ->
autofillIds.forEach { id -> autofillIds.forEach { id ->
datasetBuilder.addValueToDatasetBuilder(id, null)
var inlinePresentation: InlinePresentation? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0]
inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent)
}
}
val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Dataset.Builder(Presentations.Builder()
.apply {
inlinePresentation?.let {
setInlinePresentation(it)
}
}
.setDialogPresentation(manualSelectionView)
.build())
} else {
@Suppress("DEPRECATION")
Dataset.Builder(manualSelectionView).apply {
inlinePresentation?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setInlinePresentation(it)
}
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
datasetBuilder.setField(id, null)
} else {
@Suppress("DEPRECATION")
datasetBuilder.addValueToDatasetBuilder(id, null)
}
datasetBuilder.setAuthentication(pendingIntent.intentSender) datasetBuilder.setAuthentication(pendingIntent.intentSender)
responseBuilder.addDataset(datasetBuilder.build())
} }
val dataset = datasetBuilder.build()
Log.d(TAG, "Autofill Dataset for manual selection $dataset created")
responseBuilder.addDataset(dataset)
} }
} }
return try { return try {
responseBuilder.build() responseBuilder.build()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to create Autofill response", e)
null null
} }
} }
@@ -489,7 +535,7 @@ object AutofillHelper {
buildResponse(activity, database, entriesInfo, result, null) buildResponse(activity, database, entriesInfo, result, null)
} }
val mReplyIntent = Intent() val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.") Log.d(activity.javaClass.name, "Success Autofill auth.")
mReplyIntent.putExtra( mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT, AutofillManager.EXTRA_AUTHENTICATION_RESULT,
response) response)

View File

@@ -90,6 +90,12 @@ class KeeAutofillService : AutofillService() {
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") } cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
if (request.flags and FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST != 0) {
Log.d(TAG, "Autofill requested in compatibility mode")
} else {
Log.d(TAG, "Autofill requested in native mode")
}
// Lock // Lock
if (!mLock.get()) { if (!mLock.get()) {
mLock.set(true) mLock.set(true)
@@ -157,6 +163,7 @@ class KeeAutofillService : AutofillService() {
searchInfo: SearchInfo, searchInfo: SearchInfo,
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?, inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
callback: FillCallback) { callback: FillCallback) {
var success = false
parseResult.allAutofillIds().let { autofillIds -> parseResult.allAutofillIds().let { autofillIds ->
if (autofillIds.isNotEmpty()) { if (autofillIds.isNotEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used // If the entire Autofill Response is authenticated, AuthActivity is used
@@ -299,12 +306,16 @@ class KeeAutofillService : AutofillService() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock) responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
} }
success = true
callback.onSuccess(responseBuilder.build()) callback.onSuccess(responseBuilder.build())
} }
} }
if (!success)
callback.onFailure("Unable to get Autofill ids for UI selection")
} }
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
var success = false
if (askToSaveData) { if (askToSaveData) {
val latestStructure = request.fillContexts.last().structure val latestStructure = request.fillContexts.last().structure
StructureParser(latestStructure).parse(true)?.let { parseResult -> StructureParser(latestStructure).parse(true)?.let { parseResult ->
@@ -347,14 +358,16 @@ class KeeAutofillService : AutofillService() {
// callback.onSuccess(AutofillLauncherActivity.getAuthIntentSenderForRegistration(this, // callback.onSuccess(AutofillLauncherActivity.getAuthIntentSenderForRegistration(this,
// registerInfo)) // registerInfo))
//} else { //} else {
AutofillLauncherActivity.launchForRegistration(this, registerInfo) AutofillLauncherActivity.launchForRegistration(this, registerInfo)
callback.onSuccess() success = true
callback.onSuccess()
//} //}
return
} }
} }
} }
callback.onFailure("Saving form values is not allowed") if (!success) {
callback.onFailure("Saving form values is not allowed")
}
} }
override fun onConnected() { override fun onConnected() {

View File

@@ -36,7 +36,7 @@
android:minHeight="48dp" android:minHeight="48dp"
android:hint="@string/password" android:hint="@string/password"
android:inputType="textPassword" android:inputType="textPassword"
android:importantForAutofill="yes" android:importantForAutofill="no"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:autofillHints="password" android:autofillHints="password"