diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index cc31ac0b3..794f19946 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -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") } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/CreditCardDetailsDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/CreditCardDetailsDialogFragment.kt index 8e09fc2a1..ee4e29c3e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/CreditCardDetailsDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/CreditCardDetailsDialogFragment.kt @@ -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(5) + val year = Calendar.getInstance()[Calendar.YEAR] + for (i in years.indices) { + years[i] = (year + i).toString() + } + mCcExpirationYearSpinner?.let { spinner -> + spinner.adapter = ArrayAdapter(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 { diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt index 23aac0173..41692a15f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt @@ -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 - if (yearIndex == -1) { - yearIndex = struct.ccExpYearOptions!!.indexOf("20$yearString") - } - if (yearIndex != -1) { - builder.setValue(it, AutofillValue.forList(yearIndex)) - } else { - builder.setValue(it, AutofillValue.forText(yearString)) - } - } else { - builder.setValue(it, AutofillValue.forText(yearString)) + struct.ccExpYearOptions?.let { options -> + var yearIndex = options.indexOf(yearString) + + if (yearIndex == -1) { + yearIndex = options.indexOf("20$yearString") } + if (yearIndex != -1) { + autofillValue = AutofillValue.forList(yearIndex) + builder.setValue(it, autofillValue) + } + } + + if (autofillValue == null) { + builder.setValue(it, AutofillValue.forText("20$yearString")) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt index b41e65981..db5455282 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt @@ -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() + val requiredIds = ArrayList() + val optionalIds = ArrayList() + // 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 { - applicationId = parseResult.applicationId - webDomain = parseResult.webDomain - webScheme = parseResult.webScheme - }, + 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, diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt index d92f21400..4cbea0c97 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt @@ -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 } } diff --git a/app/src/main/java/com/kunzisoft/keepass/model/CreditCard.kt b/app/src/main/java/com/kunzisoft/keepass/model/CreditCard.kt index 49a368a3c..5a6d875e0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/CreditCard.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/CreditCard.kt @@ -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?) : 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 { override fun createFromParcel(parcel: Parcel): CreditCard { return CreditCard(parcel) diff --git a/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt index 32ff5b3e9..0cc564876 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt @@ -39,9 +39,9 @@ class EntryInfo : NodeInfo { var attachments: List = 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" diff --git a/app/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt index 496097e2d..67ee8a310 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt @@ -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 { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt index eec18660e..4c40842fd 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt @@ -357,7 +357,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context, activateCopyButton(allowCopy) assignCopyButtonClickListener(onCopyButtonClickListener) applyFontVisibility(fontInVisibility) - checkCreditCardDetails(fieldName) } entryCustomField?.let { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryField.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryField.kt index 89bfb2291..238de6883 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryField.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryField.kt @@ -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() diff --git a/app/src/main/res/layout/entry_cc_details_dialog.xml b/app/src/main/res/layout/entry_cc_details_dialog.xml index 28ed1d46a..84e4d46de 100644 --- a/app/src/main/res/layout/entry_cc_details_dialog.xml +++ b/app/src/main/res/layout/entry_cc_details_dialog.xml @@ -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" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68b9873c1..31256d451 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -530,32 +530,7 @@ KiB MiB GiB - - 01 - 02 - 03 - 04 - 05 - 06 - 07 - 08 - 09 - 10 - 11 - 12 - - - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 29 - + 5 seconds 10 seconds