mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Autofill implementation
This commit is contained in:
@@ -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?)
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user