mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Let user save credit card details after filling out new form
This commit is contained in:
@@ -191,16 +191,15 @@ class EntryEditActivity : LockingActivity(),
|
||||
val registerInfo = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
|
||||
val searchInfo: SearchInfo? = registerInfo?.searchInfo
|
||||
?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
||||
registerInfo?.username?.let {
|
||||
tempEntryInfo?.username = it
|
||||
}
|
||||
registerInfo?.password?.let {
|
||||
tempEntryInfo?.password = it
|
||||
}
|
||||
|
||||
searchInfo?.let { tempSearchInfo ->
|
||||
tempEntryInfo?.saveSearchInfo(mDatabase, tempSearchInfo)
|
||||
}
|
||||
|
||||
registerInfo?.let { regInfo ->
|
||||
tempEntryInfo?.saveRegisterInfo(mDatabase, regInfo)
|
||||
}
|
||||
|
||||
// Build fragment to manage entry modification
|
||||
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
|
||||
if (entryEditFragment == null) {
|
||||
@@ -408,7 +407,25 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
private fun addNewCreditCard() {
|
||||
val cc = CreditCard(entryEditFragment?.getExtraFields())
|
||||
var cardholder: String? = null
|
||||
var number: String? = null
|
||||
var expiration: String? = null
|
||||
var cvv: String? = null
|
||||
|
||||
entryEditFragment?.getExtraFields()?.forEach() { field ->
|
||||
when (field.name) {
|
||||
CreditCardCustomFields.CC_CARDHOLDER_FIELD_NAME ->
|
||||
cardholder = field.protectedValue.stringValue
|
||||
CreditCardCustomFields.CC_NUMBER_FIELD_NAME ->
|
||||
number = field.protectedValue.stringValue
|
||||
CreditCardCustomFields.CC_EXP_FIELD_NAME ->
|
||||
expiration = field.protectedValue.stringValue
|
||||
CreditCardCustomFields.CC_CVV_FIELD_NAME ->
|
||||
cvv = field.protectedValue.stringValue
|
||||
}
|
||||
}
|
||||
|
||||
val cc = CreditCard(cardholder, number, expiration, cvv)
|
||||
CreditCardDetailsDialogFragment.build(cc).show(supportFragmentManager, "CreditCardDialog")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
@@ -13,9 +11,10 @@ import android.widget.Spinner
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import com.kunzisoft.keepass.model.CreditCardCustomFields.buildAllFields
|
||||
import com.kunzisoft.keepass.model.CreditCard
|
||||
import com.kunzisoft.keepass.model.CreditCardCustomFields.buildAllFields
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import java.util.*
|
||||
|
||||
class CreditCardDetailsDialogFragment : DialogFragment() {
|
||||
private var mCreditCard: CreditCard? = null
|
||||
@@ -28,9 +27,6 @@ class CreditCardDetailsDialogFragment : DialogFragment() {
|
||||
private var mCcExpirationMonthSpinner: Spinner? = null
|
||||
private var mCcExpirationYearSpinner: Spinner? = null
|
||||
|
||||
private var mCcCardNumberWellFormed: Boolean = false
|
||||
private var mCcSecurityCodeWellFormed: Boolean = false
|
||||
|
||||
private var mPositiveButton: Button? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
@@ -57,8 +53,6 @@ class CreditCardDetailsDialogFragment : DialogFragment() {
|
||||
if (d != null) {
|
||||
mPositiveButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button
|
||||
mPositiveButton?.run {
|
||||
isEnabled = mCcSecurityCodeWellFormed && mCcCardNumberWellFormed
|
||||
attachListeners()
|
||||
setOnClickListener {
|
||||
submitDialog()
|
||||
}
|
||||
@@ -75,7 +69,7 @@ class CreditCardDetailsDialogFragment : DialogFragment() {
|
||||
val ccNumber = mCcCardNumber?.text?.toString() ?: ""
|
||||
|
||||
val month = mCcExpirationMonthSpinner?.selectedItem?.toString() ?: ""
|
||||
val year = mCcExpirationYearSpinner?.selectedItem?.toString() ?: ""
|
||||
val year = mCcExpirationYearSpinner?.selectedItem?.toString()?.substring(2,4) ?: ""
|
||||
|
||||
val cvv = mCcSecurityCode?.text?.toString() ?: ""
|
||||
val ccName = mCcCardholderName?.text?.toString() ?: ""
|
||||
@@ -106,37 +100,43 @@ class CreditCardDetailsDialogFragment : DialogFragment() {
|
||||
activity?.let { activity ->
|
||||
val root = activity.layoutInflater.inflate(R.layout.entry_cc_details_dialog, null)
|
||||
|
||||
mCcCardholderName = root?.findViewById(R.id.creditCardholderNameField)
|
||||
root?.run {
|
||||
mCcCardholderName = findViewById(R.id.creditCardholderNameField)
|
||||
mCcCardNumber = findViewById(R.id.creditCardNumberField)
|
||||
mCcSecurityCode = findViewById(R.id.creditCardSecurityCode)
|
||||
mCcExpirationMonthSpinner = findViewById(R.id.expirationMonth)
|
||||
mCcExpirationYearSpinner = findViewById(R.id.expirationYear)
|
||||
|
||||
mCcExpirationMonthSpinner = root?.findViewById(R.id.expirationMonth)
|
||||
mCcExpirationYearSpinner = root?.findViewById(R.id.expirationYear)
|
||||
|
||||
mCcCardNumber = root?.findViewById(R.id.creditCardNumberField)
|
||||
mCcSecurityCode = root?.findViewById(R.id.creditCardSecurityCode)
|
||||
|
||||
mCreditCard?.let {
|
||||
mCcCardholderName!!.setText(it.cardholder)
|
||||
mCcCardNumberWellFormed = true
|
||||
mCcCardNumber!!.setText(it.number)
|
||||
mCcSecurityCodeWellFormed = true
|
||||
mCcSecurityCode!!.setText(it.cvv)
|
||||
mCreditCard?.cardholder?.let {
|
||||
mCcCardholderName?.setText(it)
|
||||
}
|
||||
mCreditCard?.number?.let {
|
||||
mCcCardNumber?.setText(it)
|
||||
}
|
||||
mCreditCard?.cvv?.let {
|
||||
mCcSecurityCode?.setText(it)
|
||||
}
|
||||
}
|
||||
|
||||
val monthAdapter = ArrayAdapter.createFromResource(requireContext(),
|
||||
R.array.month_array, android.R.layout.simple_spinner_item)
|
||||
monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
mCcExpirationMonthSpinner!!.adapter = monthAdapter
|
||||
val months = arrayOf("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12")
|
||||
mCcExpirationMonthSpinner?.let { spinner ->
|
||||
spinner.adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, months)
|
||||
mCreditCard?.let { cc ->
|
||||
spinner.setSelection(getIndex(spinner, cc.getExpirationMonth()))
|
||||
}
|
||||
}
|
||||
|
||||
mCreditCard?.let { mCcExpirationMonthSpinner!!.setSelection(
|
||||
getIndex(mCcExpirationMonthSpinner!!, it.getExpirationMonth()) ) }
|
||||
|
||||
val yearAdapter = ArrayAdapter.createFromResource(requireContext(),
|
||||
R.array.year_array, android.R.layout.simple_spinner_item)
|
||||
yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
mCcExpirationYearSpinner!!.adapter = yearAdapter
|
||||
|
||||
mCreditCard?.let { mCcExpirationYearSpinner!!.setSelection(
|
||||
getIndex(mCcExpirationYearSpinner!!, it.getExpirationYear()) ) }
|
||||
val years = arrayOfNulls<String>(5)
|
||||
val year = Calendar.getInstance()[Calendar.YEAR]
|
||||
for (i in years.indices) {
|
||||
years[i] = (year + i).toString()
|
||||
}
|
||||
mCcExpirationYearSpinner?.let { spinner ->
|
||||
spinner.adapter = ArrayAdapter<String>(requireContext(), android.R.layout.simple_spinner_item, years)
|
||||
mCreditCard?.let { cc ->
|
||||
spinner.setSelection(getIndex(spinner, "20" + cc.getExpirationYear()))
|
||||
}
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
@@ -155,44 +155,14 @@ class CreditCardDetailsDialogFragment : DialogFragment() {
|
||||
|
||||
private fun getIndex(spinner: Spinner, value: String?): Int {
|
||||
for (i in 0 until spinner.count) {
|
||||
if (spinner.getItemAtPosition(i).toString().equals(value, ignoreCase = true)) {
|
||||
if (spinner.getItemAtPosition(i).toString() == value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun attachListeners() {
|
||||
mCcCardNumber?.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
val userString = s?.toString()
|
||||
mCcCardNumberWellFormed = userString?.length == 16
|
||||
mPositiveButton?.run {
|
||||
isEnabled = mCcSecurityCodeWellFormed && mCcCardNumberWellFormed
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
mCcSecurityCode?.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
val userString = s?.toString()
|
||||
mCcSecurityCodeWellFormed = (userString?.length == 3 || userString?.length == 4)
|
||||
mPositiveButton?.run {
|
||||
isEnabled = mCcSecurityCodeWellFormed && mCcCardNumberWellFormed
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val KEY_CREDIT_CARD = "KEY_CREDIT_CARD"
|
||||
|
||||
fun build(creditCard: CreditCard? = null): CreditCardDetailsDialogFragment {
|
||||
|
||||
@@ -137,14 +137,15 @@ object AutofillHelper {
|
||||
// of length four in the format MMYY
|
||||
if (field.protectedValue.stringValue.length != 4) continue
|
||||
|
||||
// get month (month in database entry is stored as String in the range 01..12)
|
||||
// get month (month in database entry is stored as String in the format MM)
|
||||
val monthString = field.protectedValue.stringValue.substring(0, 2)
|
||||
if (monthString !in context.resources.getStringArray(R.array.month_array)) continue
|
||||
val month = monthString.toIntOrNull() ?: 0
|
||||
if (month < 1 || month > 12) continue
|
||||
|
||||
val month = monthString.toInt()
|
||||
// get year (year in database entry is stored as String in the range 20..29)
|
||||
// get year (year in database entry is stored as String in the format YY)
|
||||
val yearString = field.protectedValue.stringValue.substring(2, 4)
|
||||
if (yearString !in context.resources.getStringArray(R.array.year_array)) continue
|
||||
val year = "20$yearString".toIntOrNull() ?: 0
|
||||
if (year == 0) continue
|
||||
|
||||
struct.ccExpDateId?.let {
|
||||
if (struct.isWebView) {
|
||||
@@ -154,7 +155,6 @@ object AutofillHelper {
|
||||
} else {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.clear()
|
||||
val year = "20$yearString".toInt()
|
||||
calendar[Calendar.YEAR] = year
|
||||
// Month value is 0-based. e.g., 0 for January
|
||||
calendar[Calendar.MONTH] = month - 1
|
||||
@@ -164,34 +164,33 @@ object AutofillHelper {
|
||||
}
|
||||
struct.ccExpDateMonthId?.let {
|
||||
if (struct.isWebView) {
|
||||
builder.setValue(it, AutofillValue.forText(month.toString()))
|
||||
builder.setValue(it, AutofillValue.forText(monthString))
|
||||
} else {
|
||||
if (struct.ccExpMonthOptions != null) {
|
||||
// index starts at 0
|
||||
builder.setValue(it, AutofillValue.forList(month - 1))
|
||||
} else {
|
||||
builder.setValue(it, AutofillValue.forText(month.toString()))
|
||||
builder.setValue(it, AutofillValue.forText(monthString))
|
||||
}
|
||||
}
|
||||
}
|
||||
struct.ccExpDateYearId?.let {
|
||||
if (struct.isWebView) {
|
||||
builder.setValue(it, AutofillValue.forText(yearString))
|
||||
} else {
|
||||
if (struct.ccExpYearOptions != null) {
|
||||
var yearIndex = struct.ccExpYearOptions!!.indexOf(yearString)
|
||||
var autofillValue: AutofillValue? = null
|
||||
|
||||
struct.ccExpYearOptions?.let { options ->
|
||||
var yearIndex = options.indexOf(yearString)
|
||||
|
||||
if (yearIndex == -1) {
|
||||
yearIndex = struct.ccExpYearOptions!!.indexOf("20$yearString")
|
||||
yearIndex = options.indexOf("20$yearString")
|
||||
}
|
||||
if (yearIndex != -1) {
|
||||
builder.setValue(it, AutofillValue.forList(yearIndex))
|
||||
} else {
|
||||
builder.setValue(it, AutofillValue.forText(yearString))
|
||||
autofillValue = AutofillValue.forList(yearIndex)
|
||||
builder.setValue(it, autofillValue)
|
||||
}
|
||||
} else {
|
||||
builder.setValue(it, AutofillValue.forText(yearString))
|
||||
}
|
||||
|
||||
if (autofillValue == null) {
|
||||
builder.setValue(it, AutofillValue.forText("20$yearString"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.model.CreditCard
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||
@@ -101,10 +102,15 @@ class KeeAutofillService : AutofillService() {
|
||||
callback)
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// TODO: Disable autofill for the app for API level >= 28
|
||||
// public FillResponse.Builder disableAutofill (long duration)
|
||||
// }
|
||||
// TODO does it make sense to disable autofill here? how long?
|
||||
// else {
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// val builder = FillResponse.Builder()
|
||||
// // disable for a while (duration in ms)
|
||||
// builder.disableAutofill(5*60*1000)
|
||||
// callback.onSuccess(builder.build())
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,23 +165,40 @@ class KeeAutofillService : AutofillService() {
|
||||
RemoteViews(packageName, R.layout.item_autofill_unlock)
|
||||
}
|
||||
|
||||
// Tell to service the interest to save credentials
|
||||
// Tell the autofill framework the interest to save credentials
|
||||
if (askToSaveData) {
|
||||
var types: Int = SaveInfo.SAVE_DATA_TYPE_GENERIC
|
||||
val info = ArrayList<AutofillId>()
|
||||
val requiredIds = ArrayList<AutofillId>()
|
||||
val optionalIds = ArrayList<AutofillId>()
|
||||
|
||||
// Only if at least a password
|
||||
parseResult.passwordId?.let { passwordInfo ->
|
||||
parseResult.usernameId?.let { usernameInfo ->
|
||||
types = types or SaveInfo.SAVE_DATA_TYPE_USERNAME
|
||||
info.add(usernameInfo)
|
||||
requiredIds.add(usernameInfo)
|
||||
}
|
||||
types = types or SaveInfo.SAVE_DATA_TYPE_PASSWORD
|
||||
info.add(passwordInfo)
|
||||
requiredIds.add(passwordInfo)
|
||||
}
|
||||
if (info.isNotEmpty()) {
|
||||
responseBuilder.setSaveInfo(
|
||||
SaveInfo.Builder(types, info.toTypedArray()).build()
|
||||
)
|
||||
// or a credit card form
|
||||
if (requiredIds.isEmpty()) {
|
||||
parseResult.ccnId?.let { numberId ->
|
||||
types = types or SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD
|
||||
requiredIds.add(numberId)
|
||||
Log.d(TAG, "Asking to save credit card number")
|
||||
}
|
||||
parseResult.ccExpDateId?.let { id -> optionalIds.add(id) }
|
||||
parseResult.ccExpDateYearId?.let { id -> optionalIds.add(id) }
|
||||
parseResult.ccExpDateMonthId?.let { id -> optionalIds.add(id) }
|
||||
parseResult.ccNameId?.let { id -> optionalIds.add(id) }
|
||||
parseResult.cvvId?.let { id -> optionalIds.add(id) }
|
||||
}
|
||||
if (requiredIds.isNotEmpty()) {
|
||||
val builder = SaveInfo.Builder(types, requiredIds.toTypedArray())
|
||||
if (optionalIds.isNotEmpty()) {
|
||||
builder.setOptionalIds(optionalIds.toTypedArray())
|
||||
}
|
||||
responseBuilder.setSaveInfo(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,14 +250,27 @@ class KeeAutofillService : AutofillService() {
|
||||
&& autofillAllowedFor(parseResult.webDomain, webDomainBlocklist)) {
|
||||
Log.d(TAG, "autofill onSaveRequest password")
|
||||
|
||||
|
||||
if (parseResult.ccExpirationValue == null) {
|
||||
if (parseResult.ccExpDateMonthValue != 0 && parseResult.ccExpDateYearValue != 0) {
|
||||
parseResult.ccExpirationValue = parseResult.ccExpDateMonthValue.toString().padStart(2, '0') + parseResult.ccExpDateYearValue.toString()
|
||||
}
|
||||
}
|
||||
|
||||
val creditCard = CreditCard(parseResult.ccName, parseResult.ccNumber,
|
||||
parseResult.ccExpirationValue, parseResult.cvv)
|
||||
|
||||
// Show UI to save data
|
||||
val registerInfo = RegisterInfo(SearchInfo().apply {
|
||||
val registerInfo = RegisterInfo(
|
||||
SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.webDomain
|
||||
webScheme = parseResult.webScheme
|
||||
},
|
||||
parseResult.usernameValue?.textValue?.toString(),
|
||||
parseResult.passwordValue?.textValue?.toString())
|
||||
parseResult.passwordValue?.textValue?.toString(),
|
||||
creditCard)
|
||||
|
||||
// TODO Callback in each activity #765
|
||||
//if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// callback.onSuccess(AutofillLauncherActivity.getAuthIntentSenderForRegistration(this,
|
||||
|
||||
@@ -64,7 +64,10 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return if (result?.passwordId != null || result?.ccnId != null)
|
||||
result
|
||||
else
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
@@ -140,62 +143,88 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
return true
|
||||
}
|
||||
it == "cc-name" -> {
|
||||
Log.d(TAG, "AUTOFILL cc-name hint")
|
||||
result?.ccNameId = autofillId
|
||||
result?.ccNameValue = node.autofillValue
|
||||
return true
|
||||
result?.ccName = node.autofillValue?.textValue?.toString()
|
||||
}
|
||||
it == View.AUTOFILL_HINT_CREDIT_CARD_NUMBER || it == "cc-number" -> {
|
||||
result?.ccnId = autofillId
|
||||
result?.ccnValue = node.autofillValue
|
||||
Log.d(TAG, "AUTOFILL_HINT_CREDIT_CARD_NUMBER hint")
|
||||
return true
|
||||
result?.ccnId = autofillId
|
||||
result?.ccNumber = node.autofillValue?.textValue?.toString()
|
||||
}
|
||||
it == View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE || it == "cc-exp" -> {
|
||||
// expect date string as defined in https://html.spec.whatwg.org, e.g. 2014-12
|
||||
it == "cc-exp" -> {
|
||||
Log.d(TAG, "AUTOFILL cc-exp hint")
|
||||
result?.ccExpDateId = autofillId
|
||||
node.autofillValue?.let { value ->
|
||||
if (value.isText && value.textValue.length == 7) {
|
||||
value.textValue.let { date ->
|
||||
result?.ccExpirationValue = date.substring(5, 7) + date.substring(2, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
it == View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE -> {
|
||||
Log.d(TAG, "AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE hint")
|
||||
return true
|
||||
result?.ccExpDateId = autofillId
|
||||
node.autofillValue?.let { value ->
|
||||
if (value.isDate) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.clear()
|
||||
calendar.timeInMillis = value.dateValue
|
||||
val year = calendar.get(Calendar.YEAR).toString().substring(2,4)
|
||||
val month = calendar.get(Calendar.MONTH).inc().toString().padStart(2, '0')
|
||||
result?.ccExpirationValue = month + year
|
||||
}
|
||||
}
|
||||
}
|
||||
it == View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR || it == "cc-exp-year" -> {
|
||||
Log.d(TAG, "AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR hint")
|
||||
result?.ccExpDateYearId = autofillId
|
||||
if (node.autofillValue != null) {
|
||||
if (node.autofillValue?.isText == true) {
|
||||
try {
|
||||
result?.ccExpDateYearValue =
|
||||
node.autofillValue?.textValue.toString().toInt()
|
||||
} catch (e: Exception) {
|
||||
result?.ccExpDateYearValue = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.autofillOptions != null) {
|
||||
result?.ccExpYearOptions = node.autofillOptions
|
||||
}
|
||||
return true
|
||||
node.autofillValue?.let { value ->
|
||||
var year = 0
|
||||
try {
|
||||
if (value.isText) {
|
||||
year = value.textValue.toString().toInt()
|
||||
}
|
||||
if (value.isList) {
|
||||
year = node.autofillOptions?.get(value.listValue).toString().toInt()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
year = 0
|
||||
}
|
||||
result?.ccExpDateYearValue = year % 100
|
||||
}
|
||||
}
|
||||
it == View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH || it == "cc-exp-month" -> {
|
||||
Log.d(TAG, "AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH hint")
|
||||
result?.ccExpDateMonthId = autofillId
|
||||
if (node.autofillValue != null) {
|
||||
if (node.autofillValue?.isText == true) {
|
||||
try {
|
||||
result?.ccExpDateMonthValue =
|
||||
node.autofillValue?.textValue.toString().toInt()
|
||||
} catch (e: Exception) {
|
||||
result?.ccExpDateMonthValue = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.autofillOptions != null) {
|
||||
result?.ccExpMonthOptions = node.autofillOptions
|
||||
}
|
||||
return true
|
||||
node.autofillValue?.let { value ->
|
||||
var month = 0
|
||||
if (value.isText) {
|
||||
try {
|
||||
month = value.textValue.toString().toInt()
|
||||
} catch (e: Exception) {
|
||||
month = 0
|
||||
}
|
||||
}
|
||||
if (value.isList) {
|
||||
// assume list starts with January (index 0)
|
||||
month = value.listValue + 1
|
||||
}
|
||||
result?.ccExpDateMonthValue = month
|
||||
}
|
||||
}
|
||||
it == View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE || it == "cc-csc" -> {
|
||||
result?.cvvId = autofillId
|
||||
result?.cvvValue = node.autofillValue
|
||||
Log.d(TAG, "AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE hint")
|
||||
return true
|
||||
result?.cvvId = autofillId
|
||||
result?.cvv = node.autofillValue?.textValue?.toString()
|
||||
}
|
||||
// Ignore autocomplete="off"
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
||||
@@ -409,15 +438,6 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
ccnId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
ccExpDateId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
ccExpDateYearId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
ccExpDateMonthId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
cvvId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
@@ -439,28 +459,33 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
field = value
|
||||
}
|
||||
|
||||
// stores name of cardholder
|
||||
var ccNameValue: AutofillValue? = null
|
||||
var ccName: String? = null
|
||||
set(value) {
|
||||
if (allowSaveValues && field == null)
|
||||
if (allowSaveValues)
|
||||
field = value
|
||||
}
|
||||
|
||||
// stores credit card number
|
||||
var ccnValue: AutofillValue? = null
|
||||
var ccNumber: String? = null
|
||||
set(value) {
|
||||
if (allowSaveValues && field == null)
|
||||
if (allowSaveValues)
|
||||
field = value
|
||||
}
|
||||
|
||||
// for year of CC expiration date
|
||||
// format MMYY
|
||||
var ccExpirationValue: String? = null
|
||||
set(value) {
|
||||
if (allowSaveValues)
|
||||
field = value
|
||||
}
|
||||
|
||||
// for year of CC expiration date: YY
|
||||
var ccExpDateYearValue = 0
|
||||
set(value) {
|
||||
if (allowSaveValues)
|
||||
field = value
|
||||
}
|
||||
|
||||
// for month of CC expiration date
|
||||
// for month of CC expiration date: MM
|
||||
var ccExpDateMonthValue = 0
|
||||
set(value) {
|
||||
if (allowSaveValues)
|
||||
@@ -468,9 +493,9 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
}
|
||||
|
||||
// the security code for the credit card (also called CVV)
|
||||
var cvvValue: AutofillValue? = null
|
||||
var cvv: String? = null
|
||||
set(value) {
|
||||
if (allowSaveValues && field == null)
|
||||
if (allowSaveValues)
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,34 +3,14 @@ package com.kunzisoft.keepass.model
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class CreditCard() : Parcelable {
|
||||
var cardholder: String = "";
|
||||
var number: String = "";
|
||||
var expiration: String = "";
|
||||
var cvv: String = "";
|
||||
data class CreditCard(val cardholder: String?, val number: String?,
|
||||
val expiration: String?, val cvv: String?) : Parcelable {
|
||||
|
||||
constructor(ccFields: List<Field>?) : this() {
|
||||
ccFields?.let {
|
||||
for (field in it) {
|
||||
when (field.name) {
|
||||
CreditCardCustomFields.CC_CARDHOLDER_FIELD_NAME ->
|
||||
this.cardholder = field.protectedValue.stringValue
|
||||
CreditCardCustomFields.CC_NUMBER_FIELD_NAME ->
|
||||
this.number = field.protectedValue.stringValue
|
||||
CreditCardCustomFields.CC_EXP_FIELD_NAME ->
|
||||
this.expiration = field.protectedValue.stringValue
|
||||
CreditCardCustomFields.CC_CVV_FIELD_NAME ->
|
||||
this.cvv = field.protectedValue.stringValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : this() {
|
||||
cardholder = parcel.readString() ?: cardholder
|
||||
number = parcel.readString() ?: number
|
||||
expiration = parcel.readString() ?: expiration
|
||||
cvv = parcel.readString() ?: cvv
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString()) {
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
@@ -41,7 +21,7 @@ class CreditCard() : Parcelable {
|
||||
}
|
||||
|
||||
fun getExpirationMonth(): String {
|
||||
return if (expiration.length == 4) {
|
||||
return if (expiration?.length == 4) {
|
||||
expiration.substring(0, 2)
|
||||
} else {
|
||||
""
|
||||
@@ -49,39 +29,17 @@ class CreditCard() : Parcelable {
|
||||
}
|
||||
|
||||
fun getExpirationYear(): String {
|
||||
return if (expiration.length == 4) {
|
||||
return if (expiration?.length == 4) {
|
||||
expiration.substring(2, 4)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as CreditCard
|
||||
|
||||
if (cardholder != other.cardholder) return false
|
||||
if (number != other.number) return false
|
||||
if (expiration != other.expiration) return false
|
||||
if (cvv != other.cvv) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = cardholder.hashCode()
|
||||
result = 31 * result + number.hashCode()
|
||||
result = 31 * result + expiration.hashCode()
|
||||
result = 31 * result + cvv.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<CreditCard> {
|
||||
override fun createFromParcel(parcel: Parcel): CreditCard {
|
||||
return CreditCard(parcel)
|
||||
|
||||
@@ -39,9 +39,9 @@ class EntryInfo : NodeInfo {
|
||||
var attachments: List<Attachment> = ArrayList()
|
||||
var otpModel: OtpModel? = null
|
||||
|
||||
constructor(): super()
|
||||
constructor() : super()
|
||||
|
||||
constructor(parcel: Parcel): super(parcel) {
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
id = parcel.readString() ?: id
|
||||
username = parcel.readString() ?: username
|
||||
password = parcel.readString() ?: password
|
||||
@@ -133,8 +133,7 @@ class EntryInfo : NodeInfo {
|
||||
val webDomainToStore = "$webScheme://$webDomain"
|
||||
if (database?.allowEntryCustomFields() != true || url.isEmpty()) {
|
||||
url = webDomainToStore
|
||||
}
|
||||
else if (url != webDomainToStore){
|
||||
} else if (url != webDomainToStore) {
|
||||
// Save web domain in custom field
|
||||
addUniqueField(Field(WEB_DOMAIN_FIELD_NAME,
|
||||
ProtectedString(false, webDomainToStore)),
|
||||
@@ -153,6 +152,38 @@ class EntryInfo : NodeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fun saveRegisterInfo(database: Database?, registerInfo: RegisterInfo) {
|
||||
registerInfo.username?.let {
|
||||
username = it
|
||||
}
|
||||
registerInfo.password?.let {
|
||||
password = it
|
||||
}
|
||||
|
||||
if (database?.allowEntryCustomFields() == true) {
|
||||
val creditCard: CreditCard? = registerInfo.cc
|
||||
|
||||
creditCard?.let { cc ->
|
||||
cc.cardholder?.let {
|
||||
val v = ProtectedString(false, it)
|
||||
addUniqueField(Field(CreditCardCustomFields.CC_CARDHOLDER_FIELD_NAME, v))
|
||||
}
|
||||
cc.expiration?.let {
|
||||
val v = ProtectedString(false, it)
|
||||
addUniqueField(Field(CreditCardCustomFields.CC_EXP_FIELD_NAME, v))
|
||||
}
|
||||
cc.number?.let {
|
||||
val v = ProtectedString(false, it)
|
||||
addUniqueField(Field(CreditCardCustomFields.CC_NUMBER_FIELD_NAME, v))
|
||||
}
|
||||
cc.cvv?.let {
|
||||
val v = ProtectedString(true, it)
|
||||
addUniqueField(Field(CreditCardCustomFields.CC_CVV_FIELD_NAME, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val WEB_DOMAIN_FIELD_NAME = "URL"
|
||||
|
||||
@@ -5,18 +5,21 @@ import android.os.Parcelable
|
||||
|
||||
data class RegisterInfo(val searchInfo: SearchInfo,
|
||||
val username: String?,
|
||||
val password: String?): Parcelable {
|
||||
val password: String?,
|
||||
val cc: CreditCard?): Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readParcelable(SearchInfo::class.java.classLoader) ?: SearchInfo(),
|
||||
parcel.readString() ?: "",
|
||||
parcel.readString() ?: "") {
|
||||
parcel.readString() ?: "",
|
||||
parcel.readParcelable(CreditCard::class.java.classLoader)) {
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeParcelable(searchInfo, flags)
|
||||
parcel.writeString(username)
|
||||
parcel.writeString(password)
|
||||
parcel.writeParcelable(cc, flags)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
|
||||
@@ -357,7 +357,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
activateCopyButton(allowCopy)
|
||||
assignCopyButtonClickListener(onCopyButtonClickListener)
|
||||
applyFontVisibility(fontInVisibility)
|
||||
checkCreditCardDetails(fieldName)
|
||||
}
|
||||
|
||||
entryCustomField?.let {
|
||||
|
||||
@@ -108,23 +108,6 @@ class EntryField @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
private fun setValueTextColor(color: Int) {
|
||||
valueView.setTextColor(color)
|
||||
}
|
||||
|
||||
fun checkCreditCardDetails(fieldName: String) {
|
||||
val value = valueView.text
|
||||
|
||||
when (fieldName) {
|
||||
CreditCardCustomFields.CC_CVV_FIELD_NAME ->
|
||||
if (value.length < 3 || value.length > 4) setValueTextColor(Color.RED)
|
||||
CreditCardCustomFields.CC_EXP_FIELD_NAME ->
|
||||
if (value.length != 4) setValueTextColor(Color.RED)
|
||||
CreditCardCustomFields.CC_NUMBER_FIELD_NAME ->
|
||||
if (value.length != 16) setValueTextColor(Color.RED)
|
||||
}
|
||||
}
|
||||
|
||||
fun setAutoLink() {
|
||||
if (!isProtected) linkify()
|
||||
changeProtectedValueParameters()
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:labelFor="@+id/creditCardNumberField"
|
||||
android:text="@string/cc_cardholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -62,8 +60,6 @@
|
||||
android:ems="10"
|
||||
android:focusedByDefault="true"
|
||||
android:hint="@string/cc_cardholder"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPersonName"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/creditCardholderNameLabel" />
|
||||
@@ -73,8 +69,6 @@
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:labelFor="@+id/creditCardNumberField"
|
||||
android:text="@string/cc_number"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -87,8 +81,6 @@
|
||||
android:ems="10"
|
||||
android:focusedByDefault="true"
|
||||
android:hint="@string/cc_number"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:maxLength="16"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -99,8 +91,6 @@
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:text="@string/cc_expiration"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/creditCardNumberField" />
|
||||
@@ -129,7 +119,6 @@
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAutofill="no"
|
||||
android:labelFor="@+id/creditCardSecurityCode"
|
||||
android:text="@string/cc_security_code"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -530,32 +530,7 @@
|
||||
<string name="unit_kibibyte">KiB</string>
|
||||
<string name="unit_mebibyte">MiB</string>
|
||||
<string name="unit_gibibyte">GiB</string>
|
||||
<string-array name="month_array">
|
||||
<item>01</item>
|
||||
<item>02</item>
|
||||
<item>03</item>
|
||||
<item>04</item>
|
||||
<item>05</item>
|
||||
<item>06</item>
|
||||
<item>07</item>
|
||||
<item>08</item>
|
||||
<item>09</item>
|
||||
<item>10</item>
|
||||
<item>11</item>
|
||||
<item>12</item>
|
||||
</string-array>
|
||||
<string-array name="year_array">
|
||||
<item>20</item>
|
||||
<item>21</item>
|
||||
<item>22</item>
|
||||
<item>23</item>
|
||||
<item>24</item>
|
||||
<item>25</item>
|
||||
<item>26</item>
|
||||
<item>27</item>
|
||||
<item>28</item>
|
||||
<item>29</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="timeout_options">
|
||||
<item>5 seconds</item>
|
||||
<item>10 seconds</item>
|
||||
|
||||
Reference in New Issue
Block a user