blob: 83ca42cf5356ba83e7b416465555a0047541abb1 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.preferences.autofill;
import android.app.Fragment;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.widget.FloatLabelLayout;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
/**
* Provides the Java-ui for editing a Credit Card autofill entry.
*/
public class AutofillCreditCardEditor extends Fragment implements OnItemSelectedListener,
TextWatcher {
// GUID of the card profile we are editing.
// May be the empty string if creating a new card.
private String mGUID;
private FloatLabelLayout mNameLabel;
private EditText mNameText;
private FloatLabelLayout mNumberLabel;
private EditText mNumberText;
private Spinner mExpirationMonth;
private Spinner mExpirationYear;
private int mInitialExpirationMonthPos;
private int mInitialExpirationYearPos;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View v = inflater.inflate(R.layout.autofill_credit_card_editor, container, false);
mNameLabel = (FloatLabelLayout) v.findViewById(R.id.credit_card_name_label);
mNameText = (EditText) v.findViewById(R.id.credit_card_name_edit);
mNumberLabel = (FloatLabelLayout) v.findViewById(R.id.credit_card_number_label);
mNumberText = (EditText) v.findViewById(R.id.credit_card_number_edit);
// Set text watcher to format credit card number
mNumberText.addTextChangedListener(new CreditCardNumberFormattingTextWatcher());
mExpirationMonth = (Spinner) v.findViewById(
R.id.autofill_credit_card_editor_month_spinner);
mExpirationYear = (Spinner) v.findViewById(
R.id.autofill_credit_card_editor_year_spinner);
// We know which profile to edit based on the GUID stuffed in
// our extras by AutofillPreferences.
Bundle extras = getArguments();
if (extras != null) {
mGUID = extras.getString(AutofillPreferences.AUTOFILL_GUID);
}
if (mGUID == null) {
mGUID = "";
getActivity().setTitle(R.string.autofill_create_credit_card);
} else {
getActivity().setTitle(R.string.autofill_edit_credit_card);
}
addSpinnerAdapters();
addCardDataToEditFields();
initializeSaveCancelDeleteButtons(v);
return v;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if ((parent == mExpirationYear && position != mInitialExpirationYearPos)
|| (parent == mExpirationMonth && position != mInitialExpirationMonthPos)) {
updateSaveButtonEnabled();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
updateSaveButtonEnabled();
}
void addSpinnerAdapters() {
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(getActivity(),
android.R.layout.simple_spinner_item);
// Populate the month dropdown.
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, 1);
SimpleDateFormat formatter = new SimpleDateFormat("MMMM (MM)", Locale.getDefault());
for (int month = 0; month < 12; month++) {
calendar.set(Calendar.MONTH, month);
adapter.add(formatter.format(calendar.getTime()));
}
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mExpirationMonth.setAdapter(adapter);
// Populate the year dropdown.
adapter = new ArrayAdapter<CharSequence>(getActivity(),
android.R.layout.simple_spinner_item);
int initialYear = calendar.get(Calendar.YEAR);
for (int year = initialYear; year < initialYear + 10; year++) {
adapter.add(Integer.toString(year));
}
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mExpirationYear.setAdapter(adapter);
}
private void addCardDataToEditFields() {
CreditCard card = PersonalDataManager.getInstance().getCreditCard(mGUID);
if (card == null) {
mNameLabel.focusWithoutAnimation();
return;
}
if (!TextUtils.isEmpty(card.getName())) {
mNameLabel.setText(card.getName());
}
if (!TextUtils.isEmpty(card.getNumber())) {
mNumberLabel.setText(card.getNumber());
}
// Make the name label focusable in touch mode so that mNameText doesn't get focused.
mNameLabel.getLabel().setFocusableInTouchMode(true);
int monthAsInt = 1;
if (!card.getMonth().isEmpty()) {
monthAsInt = Integer.parseInt(card.getMonth());
}
mExpirationMonth.setSelection(monthAsInt - 1);
mInitialExpirationYearPos = 0;
boolean foundYear = false;
for (int i = 0; i < mExpirationYear.getAdapter().getCount(); i++) {
if (card.getYear().equals(mExpirationYear.getAdapter().getItem(i))) {
mInitialExpirationYearPos = i;
foundYear = true;
break;
}
}
// Maybe your card expired years ago? Add the card's year
// to the spinner adapter if not found.
if (!foundYear && !card.getYear().isEmpty()) {
@SuppressWarnings("unchecked")
ArrayAdapter<CharSequence> adapter =
(ArrayAdapter<CharSequence>) mExpirationYear.getAdapter();
adapter.insert(card.getYear(), 0);
mInitialExpirationYearPos = 0;
}
mExpirationYear.setSelection(mInitialExpirationYearPos);
}
// Read edited data; save in the associated Chrome profile.
// Ignore empty fields.
private void saveCreditCard() {
// Remove all spaces in editText.
String cardNumber = mNumberText.getText().toString().replaceAll("\\s+", "");
CreditCard card = new CreditCard(mGUID, AutofillPreferences.SETTINGS_ORIGIN,
true /* isLocal */, false /* isCached */, mNameText.getText().toString().trim(),
cardNumber, "" /* obfuscatedNumber */,
String.valueOf(mExpirationMonth.getSelectedItemPosition() + 1),
(String) mExpirationYear.getSelectedItem(), "" /* basicCardPaymentType */,
0 /* issuerIconDrawableId */);
PersonalDataManager.getInstance().setCreditCard(card);
}
private void deleteCreditCard() {
if (mGUID != null) {
PersonalDataManager.getInstance().deleteCreditCard(mGUID);
}
}
private void initializeSaveCancelDeleteButtons(View v) {
Button button = (Button) v.findViewById(R.id.autofill_credit_card_delete);
if ((mGUID == null) || (mGUID.compareTo("") == 0)) {
// If this is a create, disable the delete button.
button.setEnabled(false);
} else {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AutofillCreditCardEditor.this.deleteCreditCard();
getActivity().finish();
}
});
}
button = (Button) v.findViewById(R.id.autofill_credit_card_cancel);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().finish();
}
});
button = (Button) v.findViewById(R.id.autofill_credit_card_save);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AutofillCreditCardEditor.this.saveCreditCard();
getActivity().finish();
}
});
button.setEnabled(false);
// Listen for changes to inputs. Enable the save button after something has changed.
mNameText.addTextChangedListener(this);
mNumberText.addTextChangedListener(this);
mExpirationMonth.setOnItemSelectedListener(this);
mExpirationYear.setOnItemSelectedListener(this);
}
private void updateSaveButtonEnabled() {
boolean enabled = !TextUtils.isEmpty(mNameText.getText())
|| !TextUtils.isEmpty(mNumberText.getText());
Button button = (Button) getView().findViewById(R.id.autofill_credit_card_save);
button.setEnabled(enabled);
}
/**
* Watch a TextView and if a credit card number is entered, it will format the number.
* Disable formatting when user:
* 1. Inputs dashes or spaces.
* 2. Removes separators in the middle of the string
* 3. Enters a number longer than 16 digits.
*
* Formatting will be re-enabled once text is cleared.
*/
private static class CreditCardNumberFormattingTextWatcher implements TextWatcher {
/** Character for card number section separator. */
private static final String SEPARATOR = " ";
/**
* Whether to format the credit card number. If true, spaces will be inserted
* automatically between each group of 4 digits in the credit card number as the user types.
* This is set to false if the user types a dash or deletes one of the auto-inserted spaces.
*/
private boolean mFormattingEnabled = true;
/**
* Whether the change was caused by ourselves.
* This is set true when we are manipulating the text of EditText,
* and all callback functions should check this boolean to avoid infinite recursion.
*/
private boolean mSelfChange = false;
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mSelfChange || !mFormattingEnabled) return;
// If user enters non-digit characters, do not format.
if (count > 0 && hasDashOrSpace(s, start, count)) {
mFormattingEnabled = false;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (mSelfChange || !mFormattingEnabled) return;
// If user deletes non-digit characters, do not format.
if (count > 0 && hasDashOrSpace(s, start, count)) {
mFormattingEnabled = false;
}
}
@Override
public void afterTextChanged(Editable s) {
if (mSelfChange) return;
mSelfChange = true;
if (mFormattingEnabled) {
removeSeparators(s);
// If number is too long, do not format it and remove all
// previous separators.
if (s.length() > 16) {
mFormattingEnabled = false;
} else {
insertSeparators(s);
}
}
// If user clears the input, re-enable formatting
if (s.length() == 0) mFormattingEnabled = true;
mSelfChange = false;
}
public static void removeSeparators(Editable s) {
int index = TextUtils.indexOf(s, SEPARATOR);
while (index >= 0) {
s.delete(index, index + 1);
index = TextUtils.indexOf(s, SEPARATOR, index + 1);
}
}
public static void insertSeparators(Editable s) {
final int[] positions = {4, 9, 14 };
for (int i : positions) {
if (s.length() > i) {
s.insert(i, SEPARATOR);
}
}
}
public static boolean hasDashOrSpace(final CharSequence s, final int start,
final int count) {
return TextUtils.indexOf(s, " ", start, start + count) != -1
|| TextUtils.indexOf(s, "-", start, start + count) != -1;
}
}
}