From 30ad4cd64098b05e4df0c1870f77fed9789b72b7 Mon Sep 17 00:00:00 2001 From: Davis Date: Tue, 23 Jan 2024 19:48:29 +0100 Subject: [PATCH 1/4] [CreditCard] Enhanced Compatibility with Various Card Brands Broadened support to accommodate multiple card brands. This includes handling varying card number lengths and formatting the card number to match the specific brand's format, ensuring a more versatile and user-friendly experience. --- .../details/creditcard/CreditCardDetails.java | 14 +++++--- .../text/CreditCardNumberTextWatcher.java | 35 ++++++++++++++++++- .../viewholders/SecureElementViewHolder.java | 3 +- .../creditcard/CreateCreditCardActivity.java | 6 ++-- .../creditcard/ViewCreditCardFragment.java | 2 +- .../passwordmanager/utils/CreditCardUtil.java | 21 ++++++++--- .../davis/passwordmanager/utils/card/Card.kt | 13 +++++++ .../passwordmanager/utils/card/CardFactory.kt | 7 ++++ .../passwordmanager/utils/card/CardType.kt | 22 ++++++++++++ .../passwordmanager/utils/card/Formatter.kt | 26 ++++++++++++++ .../utils/card/algorithm/LuhnAlgorithm.kt | 12 +++++++ app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 13 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/de/davis/passwordmanager/utils/card/Card.kt create mode 100644 app/src/main/java/de/davis/passwordmanager/utils/card/CardFactory.kt create mode 100644 app/src/main/java/de/davis/passwordmanager/utils/card/CardType.kt create mode 100644 app/src/main/java/de/davis/passwordmanager/utils/card/Formatter.kt create mode 100644 app/src/main/java/de/davis/passwordmanager/utils/card/algorithm/LuhnAlgorithm.kt diff --git a/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java b/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java index 7aaa3a49..3ef9b717 100644 --- a/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java +++ b/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java @@ -6,6 +6,8 @@ import de.davis.passwordmanager.database.ElementType; import de.davis.passwordmanager.database.entities.details.ElementDetail; import de.davis.passwordmanager.utils.CreditCardUtil; +import de.davis.passwordmanager.utils.card.Card; +import de.davis.passwordmanager.utils.card.CardFactory; public class CreditCardDetails implements ElementDetail { @@ -22,7 +24,7 @@ public CreditCardDetails(Name cardholder, String expirationDate, String cardNumb this.expirationDate = expirationDate; this.cardholder = cardholder; - this.cardNumber = cardNumber.replaceAll("\\s", ""); + this.cardNumber = cardNumber; this.cvv = cvv; } @@ -35,7 +37,7 @@ public void setCardholder(Name cardholder) { } public String getCardNumber() { - return cardNumber; + return getCard().getRawNumber(); } public void setCardNumber(String cardNumber) { @@ -43,11 +45,11 @@ public void setCardNumber(String cardNumber) { } public String getSecretNumber(){ - return getFormattedNumber().replaceAll("(\\d{4}\\s){3}", "**** **** **** "); + return getCard().mask(); } public String getFormattedNumber(){ - return CreditCardUtil.formatNumber(getCardNumber()); + return getCard().getCardNumber(); } public String getCvv() { @@ -71,6 +73,10 @@ public ElementType getElementType() { return ElementType.CREDIT_CARD; } + private Card getCard(){ + return CardFactory.INSTANCE.createFromCardNumber(cardNumber); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java b/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java index f79fae59..12805143 100644 --- a/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java +++ b/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java @@ -1,14 +1,24 @@ package de.davis.passwordmanager.listeners.text; import android.text.Editable; +import android.text.InputFilter; import android.text.TextWatcher; +import android.widget.EditText; import de.davis.passwordmanager.utils.CreditCardUtil; +import de.davis.passwordmanager.utils.card.CardType; +import de.davis.passwordmanager.utils.card.Formatter; public class CreditCardNumberTextWatcher implements TextWatcher { + private final EditText editText; private boolean changing; + public CreditCardNumberTextWatcher(EditText editText) { + this.editText = editText; + } + + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @@ -22,7 +32,30 @@ public void afterTextChanged(Editable s) { changing = true; - s.replace(0, s.length(), CreditCardUtil.formatNumber(s.toString())); + CardType type = CardType.Companion.getTypeByNumber(s.toString()); + int length = type.getLengthRange().getEndInclusive(); + if(type.getFormatter() instanceof Formatter.FourDigitChunkFormatter) + length += Math.floorDiv(length - 1, 4); + else + length += 2; // FourSixRemainderChunkFormatter can only add up to 2 more spaces + + String formatted = CreditCardUtil.formatNumber(s.toString()); + + if(formatted.length() > length) + formatted = formatted.substring(0, length); + + editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)}); + + + int selectionEnd = editText.getSelectionEnd(); + boolean isLast = editText.length() == selectionEnd; + + s.replace(0, s.length(), formatted); + + if(isLast) + editText.setSelection(formatted.length()); + else + editText.setSelection(Math.min(formatted.length(), selectionEnd)); changing = false; } diff --git a/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java b/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java index 72763c79..fab0358a 100644 --- a/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java +++ b/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java @@ -77,7 +77,8 @@ public void bindGeneral(@NonNull SecureElement item, String filter, OnItemClicke info.setTextColor(((PasswordDetails)item.getDetail()).getStrength().getColor(context)); }else{ CreditCardDetails details = (CreditCardDetails) item.getDetail(); - setShortenedTextIfNeeded(info, details.getSecretNumber(), details.getSecretNumber().substring(15, 19)); + String secret = details.getSecretNumber(); + setShortenedTextIfNeeded(info, secret, secret.substring(secret.lastIndexOf(" "))); info.setTextColor(MaterialColors.getColor(itemView.getContext(), com.google.android.material.R.attr.colorOnSurface, Color.BLACK)); } diff --git a/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java b/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java index 6fcde5bc..666fc422 100644 --- a/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java +++ b/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java @@ -63,7 +63,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { Objects.requireNonNull(binding.textInputLayoutCardDate.getEditText()).addTextChangedListener(new ExpiryDateTextWatcher()); - Objects.requireNonNull(binding.textInputLayoutCardNumber.getEditText()).addTextChangedListener(new CreditCardNumberTextWatcher()); + Objects.requireNonNull(binding.textInputLayoutCardNumber.getEditText()).addTextChangedListener(new CreditCardNumberTextWatcher(binding.textInputLayoutCardNumber.getEditText())); binding.textInputLayoutCardNumber.getEditText().setTransformationMethod(CreditCardNumberTransformationMethod.getInstance()); binding.textInputLayoutCardNumber.setEndIconOnClickListener(new OnCreditCardEndIconClickListener(binding.textInputLayoutCardNumber)); @@ -169,8 +169,8 @@ public CreateSecureElementActivity.Result check() { }else binding.textInputLayoutCardNumber.setErrorEnabled(false); - if(!CreditCardUtil.isValidCardNumberLength(creditCardNumber)){ - binding.textInputLayoutCardNumber.setError(getString(R.string.invalid_card_number_length)); + if(!CreditCardUtil.isValidCardNumberLength(creditCardNumber) || !CreditCardUtil.isValidCheckSum(creditCardNumber)){ + binding.textInputLayoutCardNumber.setError(getString(R.string.invalid_card)); result.setSuccess(false); }else binding.textInputLayoutCardNumber.setErrorEnabled(false); diff --git a/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/ViewCreditCardFragment.java b/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/ViewCreditCardFragment.java index 0b14b87c..c009ef19 100644 --- a/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/ViewCreditCardFragment.java +++ b/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/ViewCreditCardFragment.java @@ -47,7 +47,7 @@ public void fillInElement(@NonNull SecureElement creditCard) { TextInputLayout til = view.findViewById(R.id.textInputLayout); EditText et = til.getEditText(); et.setKeyListener(DigitsKeyListener.getInstance("0123456789 ")); - et.addTextChangedListener(new CreditCardNumberTextWatcher()); + et.addTextChangedListener(new CreditCardNumberTextWatcher(et)); til.setEndIconOnClickListener(new OnCreditCardEndIconClickListener(til)); }); binding.cardNumber.setTransformationMethod(CreditCardNumberTransformationMethod.getInstance()); diff --git a/app/src/main/java/de/davis/passwordmanager/utils/CreditCardUtil.java b/app/src/main/java/de/davis/passwordmanager/utils/CreditCardUtil.java index da5992fa..97c3b86e 100644 --- a/app/src/main/java/de/davis/passwordmanager/utils/CreditCardUtil.java +++ b/app/src/main/java/de/davis/passwordmanager/utils/CreditCardUtil.java @@ -6,6 +6,9 @@ import java.util.Date; import java.util.Locale; +import de.davis.passwordmanager.utils.card.Card; +import de.davis.passwordmanager.utils.card.CardFactory; + public class CreditCardUtil { public static boolean isValidDateFormat(String formatted){ @@ -24,14 +27,24 @@ public static boolean isValidCardNumberLength(String cardNumber){ if(cardNumber == null) return false; - String formatted = cardNumber.replaceAll("\\s", ""); + Card card = CardFactory.INSTANCE.createFromCardNumber(cardNumber); + + return card.isValidLength(); + } + + public static boolean isValidCheckSum(String cardNumber){ + if(cardNumber == null) + return false; + + Card card = CardFactory.INSTANCE.createFromCardNumber(cardNumber); - return formatted.length() == 16; + return card.isValidLuhnNumber(); } public static String formatNumber(String s){ - String f = s.replaceAll("\\s", "").replaceAll("\\d{4}", "$0 "); - return f.endsWith(" ") ? f.substring(0, f.length() -1) : f; + Card card = CardFactory.INSTANCE.createFromCardNumber(s); + + return card.getCardNumber(); } public static String formatDate(Date date){ diff --git a/app/src/main/java/de/davis/passwordmanager/utils/card/Card.kt b/app/src/main/java/de/davis/passwordmanager/utils/card/Card.kt new file mode 100644 index 00000000..9e16a759 --- /dev/null +++ b/app/src/main/java/de/davis/passwordmanager/utils/card/Card.kt @@ -0,0 +1,13 @@ +package de.davis.passwordmanager.utils.card + +import de.davis.passwordmanager.utils.card.algorithm.LuhnAlgorithm + +class Card(val rawNumber: String, val type: CardType) { + val cardNumber: String = type.formatter.format(rawNumber) + + fun mask() = type.formatter.format(rawNumber.replace("\\d(?=\\d{4})".toRegex(), "•")) + + fun isValidLuhnNumber(): Boolean = LuhnAlgorithm.isValid(rawNumber) + + fun isValidLength(): Boolean = type.lengthRange.contains(rawNumber.length) +} \ No newline at end of file diff --git a/app/src/main/java/de/davis/passwordmanager/utils/card/CardFactory.kt b/app/src/main/java/de/davis/passwordmanager/utils/card/CardFactory.kt new file mode 100644 index 00000000..3159d973 --- /dev/null +++ b/app/src/main/java/de/davis/passwordmanager/utils/card/CardFactory.kt @@ -0,0 +1,7 @@ +package de.davis.passwordmanager.utils.card + +object CardFactory { + fun createFromCardNumber(cardNumber: String): Card { + return Card(cardNumber.replace(" ", ""), CardType.getTypeByNumber(cardNumber)) + } +} \ No newline at end of file diff --git a/app/src/main/java/de/davis/passwordmanager/utils/card/CardType.kt b/app/src/main/java/de/davis/passwordmanager/utils/card/CardType.kt new file mode 100644 index 00000000..7bbaa067 --- /dev/null +++ b/app/src/main/java/de/davis/passwordmanager/utils/card/CardType.kt @@ -0,0 +1,22 @@ +package de.davis.passwordmanager.utils.card + +enum class CardType( + val prefixes: List, + val lengthRange: IntRange, + val formatter: Formatter +) { + Visa(listOf("4"), 16..16, Formatter.FourDigitChunkFormatter), + MasterCard((1..5).map { "5$it" }, 16..16, Formatter.FourDigitChunkFormatter), + AmericanExpress(listOf("34", "37"), 15..15, Formatter.FourSixRemainderChunkFormatter), + Discover(listOf("6011", "65"), 16..16, Formatter.FourDigitChunkFormatter), + JCB(listOf("35"), 16..19, Formatter.FourDigitChunkFormatter), + DinnersClub(listOf("36", "38", "39"), 14..14, Formatter.FourSixRemainderChunkFormatter), + + Unknown(emptyList(), 14..19, Formatter.FourDigitChunkFormatter); + + companion object { + fun getTypeByNumber(cardNumber: String) = entries.firstOrNull { + it.prefixes.any { prefix -> cardNumber.startsWith(prefix) } + } ?: Unknown + } +} \ No newline at end of file diff --git a/app/src/main/java/de/davis/passwordmanager/utils/card/Formatter.kt b/app/src/main/java/de/davis/passwordmanager/utils/card/Formatter.kt new file mode 100644 index 00000000..9c0a8e12 --- /dev/null +++ b/app/src/main/java/de/davis/passwordmanager/utils/card/Formatter.kt @@ -0,0 +1,26 @@ +package de.davis.passwordmanager.utils.card + +sealed class Formatter { + + data object FourDigitChunkFormatter : Formatter() { + + override fun runFormat(cardNumber: String): String = cardNumber.chunked(4).joinToString(" ") + } + + + data object FourSixRemainderChunkFormatter : Formatter() { + + override fun runFormat(cardNumber: String): String = when { + cardNumber.length <= 4 -> cardNumber + cardNumber.length <= 10 -> cardNumber.take(4) + " " + cardNumber.substring(4) + else -> cardNumber.substring(0, 4) + " " + cardNumber.substring(4, 10) + + " " + cardNumber.substring(10) + } + } + + protected abstract fun runFormat(cardNumber: String): String + + fun format(cardNumber: String): String { + return runFormat(cardNumber.replace(" ", "")) + } +} \ No newline at end of file diff --git a/app/src/main/java/de/davis/passwordmanager/utils/card/algorithm/LuhnAlgorithm.kt b/app/src/main/java/de/davis/passwordmanager/utils/card/algorithm/LuhnAlgorithm.kt new file mode 100644 index 00000000..7c902b90 --- /dev/null +++ b/app/src/main/java/de/davis/passwordmanager/utils/card/algorithm/LuhnAlgorithm.kt @@ -0,0 +1,12 @@ +package de.davis.passwordmanager.utils.card.algorithm + +object LuhnAlgorithm { + + fun isValid(cardNumber: String): Boolean { + val sanitizedNumber = cardNumber.filter { it.isDigit() } + return sanitizedNumber.reversed().mapIndexed { index, c -> + val digit = c.digitToInt() + if (index % 2 == 1) (digit * 2).let { if (it > 9) it - 9 else it } else digit + }.sum() % 10 == 0 + } +} \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8dc6932e..6fc90394 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -23,7 +23,7 @@ Passwort ist inkorrekt Passwort stimmt nicht überein Dieses Feld muss ausgefüllt sein - Kreditkartennummer muss 16 Zahlen beinhalten + Ungültige Kartennummer Ungültiges Datum Ungültige URL diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae549a13..e725f8ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,7 +38,7 @@ Incorrect password Password does not match This must be filled in - Card number must have 16 numbers + Invalid card number Invalid date Invalid url From 9fddcff580aaed6a046a1ecf0a160dbbf68f0cce Mon Sep 17 00:00:00 2001 From: Davis Date: Tue, 23 Jan 2024 20:00:42 +0100 Subject: [PATCH 2/4] [SecureElementViewHolder] Added Card type display --- .../entities/details/creditcard/CreditCardDetails.java | 2 +- .../ui/dashboard/viewholders/SecureElementViewHolder.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java b/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java index 3ef9b717..c3fa94f5 100644 --- a/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java +++ b/app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/CreditCardDetails.java @@ -73,7 +73,7 @@ public ElementType getElementType() { return ElementType.CREDIT_CARD; } - private Card getCard(){ + public Card getCard(){ return CardFactory.INSTANCE.createFromCardNumber(cardNumber); } diff --git a/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java b/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java index fab0358a..b306dd8d 100644 --- a/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java +++ b/app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/SecureElementViewHolder.java @@ -29,6 +29,7 @@ import de.davis.passwordmanager.database.entities.details.creditcard.CreditCardDetails; import de.davis.passwordmanager.database.entities.details.password.PasswordDetails; import de.davis.passwordmanager.ui.views.OptionBottomSheet; +import de.davis.passwordmanager.utils.card.CardType; public class SecureElementViewHolder extends BasicViewHolder { @@ -73,10 +74,17 @@ public void bindGeneral(@NonNull SecureElement item, String filter, OnItemClicke image.setImageDrawable(item.getIcon(context)); if(item.getElementType() == ElementType.PASSWORD){ + type.setText(item.getElementType().getTitle()); info.setText(((PasswordDetails)item.getDetail()).getStrength().getString()); info.setTextColor(((PasswordDetails)item.getDetail()).getStrength().getColor(context)); }else{ CreditCardDetails details = (CreditCardDetails) item.getDetail(); + CardType cardType = details.getCard().getType(); + if(cardType == CardType.Unknown) + type.setText(item.getElementType().getTitle()); + else + type.setText(cardType.name().replaceAll("([a-z])([A-Z])", "$1 $2")); + String secret = details.getSecretNumber(); setShortenedTextIfNeeded(info, secret, secret.substring(secret.lastIndexOf(" "))); info.setTextColor(MaterialColors.getColor(itemView.getContext(), com.google.android.material.R.attr.colorOnSurface, Color.BLACK)); From 6012421f26c5e439688672ba72215559bc525398 Mon Sep 17 00:00:00 2001 From: Davis Date: Thu, 25 Jan 2024 15:50:08 +0100 Subject: [PATCH 3/4] [SuggestedDatasetBuilder] Fixed suggestion not showing This fixes a bug where potential suggestions were not being displayed. --- .../services/autofill/builder/SuggestedDatasetBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/de/davis/passwordmanager/services/autofill/builder/SuggestedDatasetBuilder.kt b/app/src/main/java/de/davis/passwordmanager/services/autofill/builder/SuggestedDatasetBuilder.kt index 467ad0d1..b6dc187c 100644 --- a/app/src/main/java/de/davis/passwordmanager/services/autofill/builder/SuggestedDatasetBuilder.kt +++ b/app/src/main/java/de/davis/passwordmanager/services/autofill/builder/SuggestedDatasetBuilder.kt @@ -14,11 +14,11 @@ object SuggestedDatasetBuilder { builder: (TextProvider, requestCode: Int) -> Dataset ): List = url?.let { url -> SecureElementManager.getSecureElements(typeId) - .take(n) .filter { (it.detail as PasswordDetails).origin.couldBeUrl(url) || it.title.couldBeUrl(url) } + .take(n) .mapIndexed { index, element -> builder(element.getTextProvider(), index) } } ?: emptyList() From 6e9e946e3e51bb30afe2b0b78f49653f9bf9d776 Mon Sep 17 00:00:00 2001 From: Davis Date: Thu, 25 Jan 2024 16:38:01 +0100 Subject: [PATCH 4/4] [CVV length] Dynamic cvv max length based on card type Now the max length of the cvv is dynamically updated based on the card number and the associated card type. --- .../text/CreditCardNumberTextWatcher.java | 25 +++++++---- .../creditcard/CreateCreditCardActivity.java | 42 ++++++++++++------- .../res/layout/activity_create_creditcard.xml | 4 +- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java b/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java index 12805143..b4a3c34c 100644 --- a/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java +++ b/app/src/main/java/de/davis/passwordmanager/listeners/text/CreditCardNumberTextWatcher.java @@ -5,19 +5,26 @@ import android.text.TextWatcher; import android.widget.EditText; +import java.util.function.Consumer; + import de.davis.passwordmanager.utils.CreditCardUtil; import de.davis.passwordmanager.utils.card.CardType; import de.davis.passwordmanager.utils.card.Formatter; public class CreditCardNumberTextWatcher implements TextWatcher { - private final EditText editText; + private final EditText cardNumberEditText; private boolean changing; - public CreditCardNumberTextWatcher(EditText editText) { - this.editText = editText; + private Consumer onTypeDetected; + + public CreditCardNumberTextWatcher(EditText cardNumberEditText) { + this.cardNumberEditText = cardNumberEditText; } + public void setOnTypeDetected(Consumer onTypeDetected) { + this.onTypeDetected = onTypeDetected; + } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @@ -44,18 +51,20 @@ public void afterTextChanged(Editable s) { if(formatted.length() > length) formatted = formatted.substring(0, length); - editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)}); + cardNumberEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)}); + if(onTypeDetected != null) + onTypeDetected.accept(type); - int selectionEnd = editText.getSelectionEnd(); - boolean isLast = editText.length() == selectionEnd; + int selectionEnd = cardNumberEditText.getSelectionEnd(); + boolean isLast = cardNumberEditText.length() == selectionEnd; s.replace(0, s.length(), formatted); if(isLast) - editText.setSelection(formatted.length()); + cardNumberEditText.setSelection(formatted.length()); else - editText.setSelection(Math.min(formatted.length(), selectionEnd)); + cardNumberEditText.setSelection(Math.min(formatted.length(), selectionEnd)); changing = false; } diff --git a/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java b/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java index 666fc422..c7819a40 100644 --- a/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java +++ b/app/src/main/java/de/davis/passwordmanager/ui/elements/creditcard/CreateCreditCardActivity.java @@ -4,6 +4,7 @@ import android.graphics.Color; import android.os.Bundle; import android.provider.Settings; +import android.text.InputFilter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -36,6 +37,7 @@ import de.davis.passwordmanager.text.method.CreditCardNumberTransformationMethod; import de.davis.passwordmanager.ui.elements.CreateSecureElementActivity; import de.davis.passwordmanager.utils.CreditCardUtil; +import de.davis.passwordmanager.utils.card.CardType; public class CreateCreditCardActivity extends CreateSecureElementActivity { @@ -63,10 +65,20 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { Objects.requireNonNull(binding.textInputLayoutCardDate.getEditText()).addTextChangedListener(new ExpiryDateTextWatcher()); - Objects.requireNonNull(binding.textInputLayoutCardNumber.getEditText()).addTextChangedListener(new CreditCardNumberTextWatcher(binding.textInputLayoutCardNumber.getEditText())); - binding.textInputLayoutCardNumber.getEditText().setTransformationMethod(CreditCardNumberTransformationMethod.getInstance()); + CreditCardNumberTextWatcher textWatcher = new CreditCardNumberTextWatcher(binding.cardNumber); + binding.cardNumber.addTextChangedListener(textWatcher); + binding.cardNumber.setTransformationMethod(CreditCardNumberTransformationMethod.getInstance()); binding.textInputLayoutCardNumber.setEndIconOnClickListener(new OnCreditCardEndIconClickListener(binding.textInputLayoutCardNumber)); + + textWatcher.setOnTypeDetected(cardType -> { + int cvvMaxLength = cardType == CardType.AmericanExpress ? 4 : 3; + binding.cardCVV.setFilters(new InputFilter[]{new InputFilter.LengthFilter(cvvMaxLength)}); + + String cvv = Objects.requireNonNull(binding.cardCVV.getText()).toString(); + binding.cardCVV.setText(cvv.subSequence(0, Math.min(cvv.length(), cvvMaxLength))); + }); + nfcManager = new NfcManager(this) { @Override protected void cardReceived(EmvCard card, CommunicationException e) { @@ -100,10 +112,10 @@ public void fillInElement(@NonNull SecureElement element) { binding.textInputLayoutTitle.getEditText().setText(element.getTitle()); CreditCardDetails details = (CreditCardDetails) element.getDetail(); - binding.textInputLayoutUsername.getEditText().setText(details.getCardholder().getFullName()); - binding.textInputLayoutCardNumber.getEditText().setText(details.getCardNumber()); - binding.textInputLayoutCardCVV.getEditText().setText(details.getCvv()); - binding.textInputLayoutCardDate.getEditText().setText(details.getExpirationDate()); + binding.cardHolder.setText(details.getCardholder().getFullName()); + binding.cardNumber.setText(details.getCardNumber()); + binding.cardCVV.setText(details.getCvv()); + binding.expirationDate.setText(details.getExpirationDate()); } @Override @@ -154,8 +166,8 @@ public CreateSecureElementActivity.Result check() { result.setSuccess(true); String title = Objects.requireNonNull(binding.textInputLayoutTitle.getEditText()).getText().toString(); - String creditCardNumber = Objects.requireNonNull(binding.textInputLayoutCardNumber.getEditText()).getText().toString(); - String expiryDate = Objects.requireNonNull(binding.textInputLayoutCardDate.getEditText()).getText().toString(); + String creditCardNumber = Objects.requireNonNull(binding.cardNumber.getText()).toString(); + String expiryDate = Objects.requireNonNull(binding.expirationDate.getText()).toString(); if(title.isBlank()){ binding.textInputLayoutTitle.setError(getString(R.string.is_not_filled_in)); @@ -192,11 +204,11 @@ public CreateSecureElementActivity.Result check() { @Override protected SecureElement toElement() { String title = Objects.requireNonNull(binding.textInputLayoutTitle.getEditText()).getText().toString().trim(); - String creditCardNumber = Objects.requireNonNull(binding.textInputLayoutCardNumber.getEditText()).getText().toString().trim(); - String expiryDate = Objects.requireNonNull(binding.textInputLayoutCardDate.getEditText()).getText().toString().trim(); - String cvv = Objects.requireNonNull(binding.textInputLayoutCardCVV.getEditText()).getText().toString().trim(); + String creditCardNumber = Objects.requireNonNull(binding.cardNumber.getText()).toString().trim(); + String expiryDate = Objects.requireNonNull(binding.expirationDate.getText()).toString().trim(); + String cvv = Objects.requireNonNull(binding.cardCVV.getText()).toString().trim(); - Name name = Name.fromFullName(Objects.requireNonNull(binding.textInputLayoutUsername.getEditText()).getText().toString()); + Name name = Name.fromFullName(Objects.requireNonNull(binding.cardHolder.getText()).toString()); CreditCardDetails details = new CreditCardDetails(name, expiryDate, creditCardNumber, cvv); SecureElement card = getElement() == null ? @@ -220,9 +232,9 @@ private void insertCard(EmvCard card){ String cardNumber = card.getCardNumber(); String expireString = CreditCardUtil.formatDate(card.getExpireDate()); - Objects.requireNonNull(binding.textInputLayoutUsername.getEditText()).setText(name.getFullName()); - Objects.requireNonNull(binding.textInputLayoutCardNumber.getEditText()).setText(cardNumber); - Objects.requireNonNull(binding.textInputLayoutCardDate.getEditText()).setText(expireString); + binding.cardHolder.setText(name.getFullName()); + binding.cardNumber.setText(cardNumber); + binding.expirationDate.setText(expireString); } private void setNfcMessageSuccess(@StringRes int stringRes){ diff --git a/app/src/main/res/layout/activity_create_creditcard.xml b/app/src/main/res/layout/activity_create_creditcard.xml index 23f3dcba..b081b36e 100644 --- a/app/src/main/res/layout/activity_create_creditcard.xml +++ b/app/src/main/res/layout/activity_create_creditcard.xml @@ -58,7 +58,7 @@ app:startIconDrawable="@drawable/ic_baseline_person_24"> @@ -95,7 +95,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="numberPassword" - android:maxLength="3" /> + android:maxLength="4" />